| @@ -82,19 +82,19 @@ | |||
| </VS2010> | |||
| </EXPORTFORMATS> | |||
| <MODULES> | |||
| <MODULES id="juce_audio_basics" showAllCode="1" useLocalCopy="1"/> | |||
| <MODULES id="juce_audio_devices" showAllCode="1" useLocalCopy="1"/> | |||
| <MODULES id="juce_audio_formats" showAllCode="1" useLocalCopy="1"/> | |||
| <MODULES id="juce_audio_processors" showAllCode="1" useLocalCopy="1"/> | |||
| <MODULES id="juce_core" showAllCode="1" useLocalCopy="1"/> | |||
| <MODULES id="juce_cryptography" showAllCode="1" useLocalCopy="1"/> | |||
| <MODULES id="juce_data_structures" showAllCode="1" useLocalCopy="1"/> | |||
| <MODULES id="juce_events" showAllCode="1" useLocalCopy="1"/> | |||
| <MODULES id="juce_graphics" showAllCode="1" useLocalCopy="1"/> | |||
| <MODULES id="juce_gui_basics" showAllCode="1" useLocalCopy="1"/> | |||
| <MODULES id="juce_gui_extra" showAllCode="1" useLocalCopy="1"/> | |||
| <MODULES id="juce_opengl" showAllCode="1" useLocalCopy="1"/> | |||
| <MODULES id="juce_video" showAllCode="1" useLocalCopy="1"/> | |||
| <MODULES id="juce_audio_basics" showAllCode="1" useLocalCopy="0"/> | |||
| <MODULES id="juce_audio_devices" showAllCode="1" useLocalCopy="0"/> | |||
| <MODULES id="juce_audio_formats" showAllCode="1" useLocalCopy="0"/> | |||
| <MODULES id="juce_audio_processors" showAllCode="1" useLocalCopy="0"/> | |||
| <MODULES id="juce_core" showAllCode="1" useLocalCopy="0"/> | |||
| <MODULES id="juce_cryptography" showAllCode="1" useLocalCopy="0"/> | |||
| <MODULES id="juce_data_structures" showAllCode="1" useLocalCopy="0"/> | |||
| <MODULES id="juce_events" showAllCode="1" useLocalCopy="0"/> | |||
| <MODULES id="juce_graphics" showAllCode="1" useLocalCopy="0"/> | |||
| <MODULES id="juce_gui_basics" showAllCode="1" useLocalCopy="0"/> | |||
| <MODULES id="juce_gui_extra" showAllCode="1" useLocalCopy="0"/> | |||
| <MODULES id="juce_opengl" showAllCode="1" useLocalCopy="0"/> | |||
| <MODULES id="juce_video" showAllCode="1" useLocalCopy="0"/> | |||
| </MODULES> | |||
| <JUCEOPTIONS/> | |||
| </JUCERPROJECT> | |||
| @@ -1,600 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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, Random& r) | |||
| { | |||
| test (unitTest, false, r); | |||
| test (unitTest, true, r); | |||
| } | |||
| static void test (UnitTest& unitTest, bool inPlace, Random& r) | |||
| { | |||
| const int numSamples = 2048; | |||
| int32 original [numSamples], converted [numSamples], reversed [numSamples]; | |||
| { | |||
| 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, Random& r) | |||
| { | |||
| Test5 <F1, E1, FormatType, AudioData::BigEndian>::test (unitTest, r); | |||
| Test5 <F1, E1, FormatType, AudioData::LittleEndian>::test (unitTest, r); | |||
| } | |||
| }; | |||
| template <class FormatType, class Endianness> | |||
| struct Test2 | |||
| { | |||
| static void test (UnitTest& unitTest, Random& r) | |||
| { | |||
| Test3 <FormatType, Endianness, AudioData::Int8>::test (unitTest, r); | |||
| Test3 <FormatType, Endianness, AudioData::UInt8>::test (unitTest, r); | |||
| Test3 <FormatType, Endianness, AudioData::Int16>::test (unitTest, r); | |||
| Test3 <FormatType, Endianness, AudioData::Int24>::test (unitTest, r); | |||
| Test3 <FormatType, Endianness, AudioData::Int32>::test (unitTest, r); | |||
| Test3 <FormatType, Endianness, AudioData::Float32>::test (unitTest, r); | |||
| } | |||
| }; | |||
| template <class FormatType> | |||
| struct Test1 | |||
| { | |||
| static void test (UnitTest& unitTest, Random& r) | |||
| { | |||
| Test2 <FormatType, AudioData::BigEndian>::test (unitTest, r); | |||
| Test2 <FormatType, AudioData::LittleEndian>::test (unitTest, r); | |||
| } | |||
| }; | |||
| void runTest() | |||
| { | |||
| Random r = getRandom(); | |||
| beginTest ("Round-trip conversion: Int8"); | |||
| Test1 <AudioData::Int8>::test (*this, r); | |||
| beginTest ("Round-trip conversion: Int16"); | |||
| Test1 <AudioData::Int16>::test (*this, r); | |||
| beginTest ("Round-trip conversion: Int24"); | |||
| Test1 <AudioData::Int24>::test (*this, r); | |||
| beginTest ("Round-trip conversion: Int32"); | |||
| Test1 <AudioData::Int32>::test (*this, r); | |||
| beginTest ("Round-trip conversion: Float32"); | |||
| Test1 <AudioData::Float32>::test (*this, r); | |||
| } | |||
| }; | |||
| static AudioConversionTests audioConversionUnitTests; | |||
| #endif | |||
| @@ -1,710 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 }; | |||
| }; | |||
| /** A 32-bit integer type, of which only the bottom 24 bits are used. */ | |||
| class Int24in32 : public Int32 | |||
| { | |||
| public: | |||
| inline Int24in32 (void* d) noexcept : Int32 (d) {} | |||
| 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) << 8; } | |||
| inline int32 getAsInt32BE() const noexcept { return (int32) ByteOrder::swapIfLittleEndian (*data) << 8; } | |||
| inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) newValue >> 8); } | |||
| inline void setAsInt32BE (int32 newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) newValue >> 8); } | |||
| 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 (Int24in32& source) noexcept { *data = *source.data; } | |||
| enum { bytesPerSample = 4, maxValue = 0x7fffff, resolution = (1 << 8), 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) | |||
| {} | |||
| void convertSamples (void* dest, const void* source, int numSamples) const override | |||
| { | |||
| 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 override | |||
| { | |||
| 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 | |||
| @@ -1,670 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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() noexcept | |||
| : numChannels (0), size (0), allocatedBytes (0), | |||
| channels (static_cast<float**> (preallocatedChannelSpace)), | |||
| isClear (false) | |||
| { | |||
| } | |||
| 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(); | |||
| if (other.isClear) | |||
| { | |||
| clear(); | |||
| } | |||
| else | |||
| { | |||
| 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; | |||
| isClear = false; | |||
| } | |||
| AudioSampleBuffer::AudioSampleBuffer (float* const* dataToReferTo, | |||
| const int numChans, | |||
| const int numSamples) noexcept | |||
| : numChannels (numChans), | |||
| size (numSamples), | |||
| allocatedBytes (0) | |||
| { | |||
| jassert (dataToReferTo != nullptr); | |||
| jassert (numChans >= 0 && numSamples >= 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), | |||
| isClear (false) | |||
| { | |||
| jassert (dataToReferTo != nullptr); | |||
| jassert (numChans >= 0 && startSample >= 0 && numSamples >= 0); | |||
| allocateChannels (dataToReferTo, startSample); | |||
| } | |||
| void AudioSampleBuffer::setDataToReferTo (float** dataToReferTo, | |||
| const int newNumChannels, | |||
| const int newNumSamples) noexcept | |||
| { | |||
| jassert (dataToReferTo != nullptr); | |||
| jassert (newNumChannels >= 0 && newNumSamples >= 0); | |||
| allocatedBytes = 0; | |||
| allocatedData.free(); | |||
| numChannels = newNumChannels; | |||
| size = newNumSamples; | |||
| allocateChannels (dataToReferTo, 0); | |||
| jassert (! isClear); | |||
| } | |||
| void AudioSampleBuffer::allocateChannels (float* const* const dataToReferTo, int offset) | |||
| { | |||
| jassert (offset >= 0); | |||
| // (try to avoid doing a malloc here, as that'll blow up things like Pro-Tools) | |||
| if (numChannels < (int) numElementsInArray (preallocatedChannelSpace)) | |||
| { | |||
| channels = static_cast<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; | |||
| isClear = false; | |||
| } | |||
| AudioSampleBuffer& AudioSampleBuffer::operator= (const AudioSampleBuffer& other) noexcept | |||
| { | |||
| if (this != &other) | |||
| { | |||
| setSize (other.getNumChannels(), other.getNumSamples(), false, false, false); | |||
| if (other.isClear) | |||
| { | |||
| clear(); | |||
| } | |||
| else | |||
| { | |||
| isClear = false; | |||
| for (int i = 0; i < numChannels; ++i) | |||
| 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 = ((size_t) newNumSamples + 3) & ~3u; | |||
| const size_t channelListSize = ((sizeof (float*) * (size_t) (newNumChannels + 1)) + 15) & ~15u; | |||
| const size_t newTotalBytes = ((size_t) newNumChannels * (size_t) allocatedSamplesPerChannel * sizeof (float)) | |||
| + channelListSize + 32; | |||
| if (keepExistingContent) | |||
| { | |||
| HeapBlock<char, true> newData; | |||
| newData.allocate (newTotalBytes, clearExtraSpace || isClear); | |||
| const size_t numSamplesToCopy = (size_t) 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; | |||
| } | |||
| if (! isClear) | |||
| { | |||
| 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 || isClear) | |||
| allocatedData.clear (newTotalBytes); | |||
| } | |||
| else | |||
| { | |||
| allocatedBytes = newTotalBytes; | |||
| allocatedData.allocate (newTotalBytes, clearExtraSpace || isClear); | |||
| 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 | |||
| { | |||
| if (! isClear) | |||
| { | |||
| for (int i = 0; i < numChannels; ++i) | |||
| FloatVectorOperations::clear (channels[i], size); | |||
| isClear = true; | |||
| } | |||
| } | |||
| void AudioSampleBuffer::clear (const int startSample, | |||
| const int numSamples) noexcept | |||
| { | |||
| jassert (startSample >= 0 && startSample + numSamples <= size); | |||
| if (! isClear) | |||
| { | |||
| if (startSample == 0 && numSamples == size) | |||
| isClear = true; | |||
| for (int i = 0; i < numChannels; ++i) | |||
| 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); | |||
| if (! isClear) | |||
| FloatVectorOperations::clear (channels [channel] + startSample, numSamples); | |||
| } | |||
| float AudioSampleBuffer::getSample (int channel, int index) const noexcept | |||
| { | |||
| jassert (isPositiveAndBelow (channel, numChannels)); | |||
| jassert (isPositiveAndBelow (index, size)); | |||
| return *(channels [channel] + index); | |||
| } | |||
| void AudioSampleBuffer::setSample (int channel, int index, float newValue) noexcept | |||
| { | |||
| jassert (isPositiveAndBelow (channel, numChannels)); | |||
| jassert (isPositiveAndBelow (index, size)); | |||
| *(channels [channel] + index) = newValue; | |||
| isClear = false; | |||
| } | |||
| void AudioSampleBuffer::addSample (int channel, int index, float valueToAdd) noexcept | |||
| { | |||
| jassert (isPositiveAndBelow (channel, numChannels)); | |||
| jassert (isPositiveAndBelow (index, size)); | |||
| *(channels [channel] + index) += valueToAdd; | |||
| isClear = false; | |||
| } | |||
| 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 && ! isClear) | |||
| { | |||
| 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 (! isClear) | |||
| { | |||
| if (startGain == endGain) | |||
| { | |||
| applyGain (channel, startSample, numSamples, startGain); | |||
| } | |||
| else | |||
| { | |||
| jassert (isPositiveAndBelow (channel, numChannels)); | |||
| jassert (startSample >= 0 && startSample + numSamples <= size); | |||
| const float increment = (endGain - startGain) / numSamples; | |||
| float* d = channels [channel] + startSample; | |||
| while (--numSamples >= 0) | |||
| { | |||
| *d++ *= startGain; | |||
| startGain += increment; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| void AudioSampleBuffer::applyGain (int startSample, int numSamples, 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 (int startSample, int numSamples, | |||
| float startGain, 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 && ! source.isClear) | |||
| { | |||
| float* const d = channels [destChannel] + destStartSample; | |||
| const float* const s = source.channels [sourceChannel] + sourceStartSample; | |||
| if (isClear) | |||
| { | |||
| isClear = false; | |||
| if (gain != 1.0f) | |||
| FloatVectorOperations::copyWithMultiply (d, s, gain, numSamples); | |||
| else | |||
| FloatVectorOperations::copy (d, s, numSamples); | |||
| } | |||
| else | |||
| { | |||
| 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 (isClear) | |||
| { | |||
| isClear = false; | |||
| if (gain != 1.0f) | |||
| FloatVectorOperations::copyWithMultiply (d, source, gain, numSamples); | |||
| else | |||
| FloatVectorOperations::copy (d, source, numSamples); | |||
| } | |||
| else | |||
| { | |||
| 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)) | |||
| { | |||
| isClear = false; | |||
| 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) | |||
| { | |||
| if (source.isClear) | |||
| { | |||
| if (! isClear) | |||
| FloatVectorOperations::clear (channels [destChannel] + destStartSample, numSamples); | |||
| } | |||
| else | |||
| { | |||
| isClear = false; | |||
| 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) | |||
| { | |||
| isClear = false; | |||
| 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* const d = channels [destChannel] + destStartSample; | |||
| if (gain != 1.0f) | |||
| { | |||
| if (gain == 0) | |||
| { | |||
| if (! isClear) | |||
| FloatVectorOperations::clear (d, numSamples); | |||
| } | |||
| else | |||
| { | |||
| isClear = false; | |||
| FloatVectorOperations::copyWithMultiply (d, source, gain, numSamples); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| isClear = false; | |||
| 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)) | |||
| { | |||
| isClear = false; | |||
| const float increment = (endGain - startGain) / numSamples; | |||
| float* d = channels [destChannel] + destStartSample; | |||
| while (--numSamples >= 0) | |||
| { | |||
| *d++ = startGain * *source++; | |||
| startGain += increment; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| void AudioSampleBuffer::reverse (int channel, int startSample, int numSamples) const noexcept | |||
| { | |||
| jassert (isPositiveAndBelow (channel, numChannels)); | |||
| jassert (startSample >= 0 && startSample + numSamples <= size); | |||
| if (! isClear) | |||
| std::reverse (channels[channel] + startSample, | |||
| channels[channel] + startSample + numSamples); | |||
| } | |||
| void AudioSampleBuffer::reverse (int startSample, int numSamples) const noexcept | |||
| { | |||
| for (int i = 0; i < numChannels; ++i) | |||
| reverse (i, startSample, numSamples); | |||
| } | |||
| Range<float> AudioSampleBuffer::findMinMax (const int channel, | |||
| const int startSample, | |||
| int numSamples) const noexcept | |||
| { | |||
| jassert (isPositiveAndBelow (channel, numChannels)); | |||
| jassert (startSample >= 0 && startSample + numSamples <= size); | |||
| if (isClear) | |||
| return Range<float>(); | |||
| return FloatVectorOperations::findMinAndMax (channels [channel] + startSample, numSamples); | |||
| } | |||
| float AudioSampleBuffer::getMagnitude (const int channel, | |||
| const int startSample, | |||
| const int numSamples) const noexcept | |||
| { | |||
| jassert (isPositiveAndBelow (channel, numChannels)); | |||
| jassert (startSample >= 0 && startSample + numSamples <= size); | |||
| if (isClear) | |||
| return 0.0f; | |||
| const Range<float> r (findMinMax (channel, startSample, numSamples)); | |||
| return jmax (r.getStart(), -r.getStart(), r.getEnd(), -r.getEnd()); | |||
| } | |||
| float AudioSampleBuffer::getMagnitude (int startSample, int numSamples) const noexcept | |||
| { | |||
| float mag = 0.0f; | |||
| if (! isClear) | |||
| for (int i = 0; i < numChannels; ++i) | |||
| mag = jmax (mag, getMagnitude (i, startSample, numSamples)); | |||
| return mag; | |||
| } | |||
| 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 || isClear) | |||
| return 0.0f; | |||
| const float* const data = channels [channel] + startSample; | |||
| double sum = 0.0; | |||
| for (int i = 0; i < numSamples; ++i) | |||
| { | |||
| const float sample = data [i]; | |||
| sum += sample * sample; | |||
| } | |||
| return (float) std::sqrt (sum / numSamples); | |||
| } | |||
| @@ -1,526 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 an empty buffer with 0 channels and 0 length. */ | |||
| AudioSampleBuffer() noexcept; | |||
| //============================================================================== | |||
| /** 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&) noexcept; | |||
| /** Copies another buffer onto this one. | |||
| This buffer's size will be changed to that of the other buffer. | |||
| */ | |||
| AudioSampleBuffer& operator= (const AudioSampleBuffer&) 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 to an array of read-only samples in one of the buffer's channels. | |||
| For speed, this doesn't check whether the channel number is out of range, | |||
| so be careful when using it! | |||
| If you need to write to the data, do NOT call this method and const_cast the | |||
| result! Instead, you must call getWritePointer so that the buffer knows you're | |||
| planning on modifying the data. | |||
| */ | |||
| const float* getReadPointer (int channelNumber) const noexcept | |||
| { | |||
| jassert (isPositiveAndBelow (channelNumber, numChannels)); | |||
| return channels [channelNumber]; | |||
| } | |||
| /** Returns a pointer to an array of read-only samples in one of the buffer's channels. | |||
| For speed, this doesn't check whether the channel number or index are out of range, | |||
| so be careful when using it! | |||
| If you need to write to the data, do NOT call this method and const_cast the | |||
| result! Instead, you must call getWritePointer so that the buffer knows you're | |||
| planning on modifying the data. | |||
| */ | |||
| const float* getReadPointer (int channelNumber, int sampleIndex) const noexcept | |||
| { | |||
| jassert (isPositiveAndBelow (channelNumber, numChannels)); | |||
| jassert (isPositiveAndBelow (sampleIndex, size)); | |||
| return channels [channelNumber] + sampleIndex; | |||
| } | |||
| /** Returns a writeable pointer to one of the buffer's channels. | |||
| For speed, this doesn't check whether the channel number is out of range, | |||
| so be careful when using it! | |||
| Note that if you're not planning on writing to the data, you should always | |||
| use getReadPointer instead. | |||
| */ | |||
| float* getWritePointer (int channelNumber) noexcept | |||
| { | |||
| jassert (isPositiveAndBelow (channelNumber, numChannels)); | |||
| isClear = false; | |||
| return channels [channelNumber]; | |||
| } | |||
| /** Returns a writeable pointer to one of the buffer's channels. | |||
| For speed, this doesn't check whether the channel number or index are out of range, | |||
| so be careful when using it! | |||
| Note that if you're not planning on writing to the data, you should | |||
| use getReadPointer instead. | |||
| */ | |||
| float* getWritePointer (int channelNumber, int sampleIndex) noexcept | |||
| { | |||
| jassert (isPositiveAndBelow (channelNumber, numChannels)); | |||
| jassert (isPositiveAndBelow (sampleIndex, size)); | |||
| isClear = false; | |||
| return channels [channelNumber] + sampleIndex; | |||
| } | |||
| /** Returns an array of pointers to the channels in the buffer. | |||
| Don't modify any of the pointers that are returned, and bear in mind that | |||
| these will become invalid if the buffer is resized. | |||
| */ | |||
| const float** getArrayOfReadPointers() const noexcept { return const_cast<const float**> (channels); } | |||
| /** Returns an array of pointers to the channels in the buffer. | |||
| Don't modify any of the pointers that are returned, and bear in mind that | |||
| these will become invalid if the buffer is resized. | |||
| */ | |||
| float** getArrayOfWritePointers() noexcept { isClear = false; return channels; } | |||
| //============================================================================== | |||
| /** Changes the buffer's size or number of channels. | |||
| This can expand or contract the buffer's length, and add or remove channels. | |||
| If keepExistingContent is true, it will try to preserve as much of the | |||
| old data as it can in the new buffer. | |||
| If clearExtraSpace is true, then any extra channels or space that is | |||
| allocated will be also be cleared. If false, then this space is left | |||
| uninitialised. | |||
| If avoidReallocating is true, then changing the buffer's size won't reduce the | |||
| amount of memory that is currently allocated (but it will still increase it if | |||
| the new size is bigger than the amount it currently has). If this is false, then | |||
| a new allocation will be done so that the buffer uses takes up the minimum amount | |||
| of memory that it needs. | |||
| If the required memory can't be allocated, this will throw a std::bad_alloc exception. | |||
| */ | |||
| void setSize (int newNumChannels, | |||
| int newNumSamples, | |||
| bool keepExistingContent = false, | |||
| bool clearExtraSpace = false, | |||
| bool avoidReallocating = false) noexcept; | |||
| /** 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; | |||
| /** Returns true if the buffer has been entirely cleared. | |||
| Note that this does not actually measure the contents of the buffer - it simply | |||
| returns a flag that is set when the buffer is cleared, and which is reset whenever | |||
| functions like getWritePointer() are invoked. That means the method does not take | |||
| any time, but it may return false negatives when in fact the buffer is still empty. | |||
| */ | |||
| bool hasBeenCleared() const noexcept { return isClear; } | |||
| //============================================================================== | |||
| /** Returns a sample from the buffer. | |||
| The channel and index are not checked - they are expected to be in-range. If not, | |||
| an assertion will be thrown, but in a release build, you're into 'undefined behaviour' | |||
| territory. | |||
| */ | |||
| float getSample (int channel, int sampleIndex) const noexcept; | |||
| /** Sets a sample in the buffer. | |||
| The channel and index are not checked - they are expected to be in-range. If not, | |||
| an assertion will be thrown, but in a release build, you're into 'undefined behaviour' | |||
| territory. | |||
| */ | |||
| void setSample (int destChannel, int destSample, float newValue) noexcept; | |||
| /** Adds a value to a sample in the buffer. | |||
| The channel and index are not checked - they are expected to be in-range. If not, | |||
| an assertion will be thrown, but in a release build, you're into 'undefined behaviour' | |||
| territory. | |||
| */ | |||
| void addSample (int destChannel, int destSample, float valueToAdd) noexcept; | |||
| /** 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; | |||
| /** Returns a Range indicating the lowest and highest sample values in a given section. | |||
| @param channel the channel to read from | |||
| @param startSample the start sample within the channel | |||
| @param numSamples the number of samples to check | |||
| */ | |||
| Range<float> findMinMax (int channel, | |||
| int startSample, | |||
| int numSamples) 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; | |||
| /** Reverses a part of a channel. */ | |||
| void reverse (int channel, int startSample, int numSamples) const noexcept; | |||
| /** Reverses a part of the buffer. */ | |||
| void reverse (int startSample, int numSamples) const noexcept; | |||
| //============================================================================== | |||
| #ifndef DOXYGEN | |||
| // Note that these methods have now been replaced by getReadPointer() and getWritePointer() | |||
| JUCE_DEPRECATED_WITH_BODY (const float* getSampleData (int channel) const, { return getReadPointer (channel); }) | |||
| JUCE_DEPRECATED_WITH_BODY (const float* getSampleData (int channel, int index) const, { return getReadPointer (channel, index); }) | |||
| JUCE_DEPRECATED_WITH_BODY (float* getSampleData (int channel), { return getWritePointer (channel); }) | |||
| JUCE_DEPRECATED_WITH_BODY (float* getSampleData (int channel, int index), { return getWritePointer (channel, index); }) | |||
| // These have been replaced by getArrayOfReadPointers() and getArrayOfWritePointers() | |||
| JUCE_DEPRECATED_WITH_BODY (const float** getArrayOfChannels() const, { return getArrayOfReadPointers(); }) | |||
| JUCE_DEPRECATED_WITH_BODY (float** getArrayOfChannels(), { return getArrayOfWritePointers(); }) | |||
| #endif | |||
| private: | |||
| //============================================================================== | |||
| int numChannels, size; | |||
| size_t allocatedBytes; | |||
| float** channels; | |||
| HeapBlock<char, true> allocatedData; | |||
| float* preallocatedChannelSpace [32]; | |||
| bool isClear; | |||
| void allocateData(); | |||
| void allocateChannels (float* const*, int offset); | |||
| JUCE_LEAK_DETECTOR (AudioSampleBuffer) | |||
| }; | |||
| #endif // JUCE_AUDIOSAMPLEBUFFER_H_INCLUDED | |||
| @@ -1,893 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 FloatVectorHelpers | |||
| { | |||
| #define JUCE_INCREMENT_SRC_DEST dest += (16 / sizeof (*dest)); src += (16 / sizeof (*dest)); | |||
| #define JUCE_INCREMENT_SRC1_SRC2_DEST dest += (16 / sizeof (*dest)); src1 += (16 / sizeof (*dest)); src2 += (16 / sizeof (*dest)); | |||
| #define JUCE_INCREMENT_DEST dest += (16 / sizeof (*dest)); | |||
| #if JUCE_USE_SSE_INTRINSICS | |||
| 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; | |||
| } | |||
| struct BasicOps32 | |||
| { | |||
| typedef float Type; | |||
| typedef __m128 ParallelType; | |||
| enum { numParallel = 4 }; | |||
| static forcedinline ParallelType load1 (Type v) noexcept { return _mm_load1_ps (&v); } | |||
| static forcedinline ParallelType loadA (const Type* v) noexcept { return _mm_load_ps (v); } | |||
| static forcedinline ParallelType loadU (const Type* v) noexcept { return _mm_loadu_ps (v); } | |||
| static forcedinline void storeA (Type* dest, ParallelType a) noexcept { _mm_store_ps (dest, a); } | |||
| static forcedinline void storeU (Type* dest, ParallelType a) noexcept { _mm_storeu_ps (dest, a); } | |||
| static forcedinline ParallelType add (ParallelType a, ParallelType b) noexcept { return _mm_add_ps (a, b); } | |||
| static forcedinline ParallelType sub (ParallelType a, ParallelType b) noexcept { return _mm_sub_ps (a, b); } | |||
| static forcedinline ParallelType mul (ParallelType a, ParallelType b) noexcept { return _mm_mul_ps (a, b); } | |||
| static forcedinline ParallelType max (ParallelType a, ParallelType b) noexcept { return _mm_max_ps (a, b); } | |||
| static forcedinline ParallelType min (ParallelType a, ParallelType b) noexcept { return _mm_min_ps (a, b); } | |||
| static forcedinline Type max (ParallelType a) noexcept { Type v[numParallel]; storeU (v, a); return jmax (v[0], v[1], v[2], v[3]); } | |||
| static forcedinline Type min (ParallelType a) noexcept { Type v[numParallel]; storeU (v, a); return jmin (v[0], v[1], v[2], v[3]); } | |||
| }; | |||
| struct BasicOps64 | |||
| { | |||
| typedef double Type; | |||
| typedef __m128d ParallelType; | |||
| enum { numParallel = 2 }; | |||
| static forcedinline ParallelType load1 (Type v) noexcept { return _mm_load1_pd (&v); } | |||
| static forcedinline ParallelType loadA (const Type* v) noexcept { return _mm_load_pd (v); } | |||
| static forcedinline ParallelType loadU (const Type* v) noexcept { return _mm_loadu_pd (v); } | |||
| static forcedinline void storeA (Type* dest, ParallelType a) noexcept { _mm_store_pd (dest, a); } | |||
| static forcedinline void storeU (Type* dest, ParallelType a) noexcept { _mm_storeu_pd (dest, a); } | |||
| static forcedinline ParallelType add (ParallelType a, ParallelType b) noexcept { return _mm_add_pd (a, b); } | |||
| static forcedinline ParallelType sub (ParallelType a, ParallelType b) noexcept { return _mm_sub_pd (a, b); } | |||
| static forcedinline ParallelType mul (ParallelType a, ParallelType b) noexcept { return _mm_mul_pd (a, b); } | |||
| static forcedinline ParallelType max (ParallelType a, ParallelType b) noexcept { return _mm_max_pd (a, b); } | |||
| static forcedinline ParallelType min (ParallelType a, ParallelType b) noexcept { return _mm_min_pd (a, b); } | |||
| static forcedinline Type max (ParallelType a) noexcept { Type v[numParallel]; storeU (v, a); return jmax (v[0], v[1]); } | |||
| static forcedinline Type min (ParallelType a) noexcept { Type v[numParallel]; storeU (v, a); return jmin (v[0], v[1]); } | |||
| }; | |||
| #define JUCE_BEGIN_VEC_OP \ | |||
| typedef FloatVectorHelpers::ModeType<sizeof(*dest)>::Mode Mode; \ | |||
| if (FloatVectorHelpers::isSSE2Available()) \ | |||
| { \ | |||
| const int numLongOps = num / Mode::numParallel; | |||
| #define JUCE_FINISH_VEC_OP(normalOp) \ | |||
| num &= (Mode::numParallel - 1); \ | |||
| if (num == 0) return; \ | |||
| } \ | |||
| for (int i = 0; i < num; ++i) normalOp; | |||
| #define JUCE_PERFORM_VEC_OP_DEST(normalOp, vecOp, locals, setupOp) \ | |||
| JUCE_BEGIN_VEC_OP \ | |||
| setupOp \ | |||
| if (FloatVectorHelpers::isAligned (dest)) JUCE_VEC_LOOP (vecOp, dummy, Mode::loadA, Mode::storeA, locals, JUCE_INCREMENT_DEST) \ | |||
| else JUCE_VEC_LOOP (vecOp, dummy, Mode::loadU, Mode::storeU, locals, JUCE_INCREMENT_DEST) \ | |||
| JUCE_FINISH_VEC_OP (normalOp) | |||
| #define JUCE_PERFORM_VEC_OP_SRC_DEST(normalOp, vecOp, locals, increment, setupOp) \ | |||
| JUCE_BEGIN_VEC_OP \ | |||
| setupOp \ | |||
| if (FloatVectorHelpers::isAligned (dest)) \ | |||
| { \ | |||
| if (FloatVectorHelpers::isAligned (src)) JUCE_VEC_LOOP (vecOp, Mode::loadA, Mode::loadA, Mode::storeA, locals, increment) \ | |||
| else JUCE_VEC_LOOP (vecOp, Mode::loadU, Mode::loadA, Mode::storeA, locals, increment) \ | |||
| }\ | |||
| else \ | |||
| { \ | |||
| if (FloatVectorHelpers::isAligned (src)) JUCE_VEC_LOOP (vecOp, Mode::loadA, Mode::loadU, Mode::storeU, locals, increment) \ | |||
| else JUCE_VEC_LOOP (vecOp, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \ | |||
| } \ | |||
| JUCE_FINISH_VEC_OP (normalOp) | |||
| #define JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST(normalOp, vecOp, locals, increment, setupOp) \ | |||
| JUCE_BEGIN_VEC_OP \ | |||
| setupOp \ | |||
| { \ | |||
| Mode::ParallelType (&loadSrc1) (const Mode::Type* v) = FloatVectorHelpers::isAligned (src1) ? Mode::loadA : Mode::loadU; \ | |||
| Mode::ParallelType (&loadSrc2) (const Mode::Type* v) = FloatVectorHelpers::isAligned (src2) ? Mode::loadA : Mode::loadU; \ | |||
| void (&storeDst) (Mode::Type* dest, Mode::ParallelType a) = FloatVectorHelpers::isAligned (dest) ? Mode::storeA : Mode::storeU; \ | |||
| JUCE_VEC_LOOP_TWO_SOURCES (vecOp, loadSrc1, loadSrc2, storeDst, locals, increment); \ | |||
| } \ | |||
| JUCE_FINISH_VEC_OP (normalOp) | |||
| //============================================================================== | |||
| #elif JUCE_USE_ARM_NEON | |||
| struct BasicOps32 | |||
| { | |||
| typedef float Type; | |||
| typedef float32x4_t ParallelType; | |||
| enum { numParallel = 4 }; | |||
| static forcedinline ParallelType load1 (Type v) noexcept { return vld1q_dup_f32 (&v); } | |||
| static forcedinline ParallelType loadA (const Type* v) noexcept { return vld1q_f32 (v); } | |||
| static forcedinline ParallelType loadU (const Type* v) noexcept { return vld1q_f32 (v); } | |||
| static forcedinline void storeA (Type* dest, ParallelType a) noexcept { vst1q_f32 (dest, a); } | |||
| static forcedinline void storeU (Type* dest, ParallelType a) noexcept { vst1q_f32 (dest, a); } | |||
| static forcedinline ParallelType add (ParallelType a, ParallelType b) noexcept { return vaddq_f32 (a, b); } | |||
| static forcedinline ParallelType sub (ParallelType a, ParallelType b) noexcept { return vsubq_f32 (a, b); } | |||
| static forcedinline ParallelType mul (ParallelType a, ParallelType b) noexcept { return vmulq_f32 (a, b); } | |||
| static forcedinline ParallelType max (ParallelType a, ParallelType b) noexcept { return vmaxq_f32 (a, b); } | |||
| static forcedinline ParallelType min (ParallelType a, ParallelType b) noexcept { return vminq_f32 (a, b); } | |||
| static forcedinline Type max (ParallelType a) noexcept { Type v[numParallel]; storeU (v, a); return jmax (v[0], v[1], v[2], v[3]); } | |||
| static forcedinline Type min (ParallelType a) noexcept { Type v[numParallel]; storeU (v, a); return jmin (v[0], v[1], v[2], v[3]); } | |||
| }; | |||
| struct BasicOps64 | |||
| { | |||
| typedef double Type; | |||
| typedef double ParallelType; | |||
| enum { numParallel = 1 }; | |||
| static forcedinline ParallelType load1 (Type v) noexcept { return v; } | |||
| static forcedinline ParallelType loadA (const Type* v) noexcept { return *v; } | |||
| static forcedinline ParallelType loadU (const Type* v) noexcept { return *v; } | |||
| static forcedinline void storeA (Type* dest, ParallelType a) noexcept { *dest = a; } | |||
| static forcedinline void storeU (Type* dest, ParallelType a) noexcept { *dest = a; } | |||
| static forcedinline ParallelType add (ParallelType a, ParallelType b) noexcept { return a + b; } | |||
| static forcedinline ParallelType sub (ParallelType a, ParallelType b) noexcept { return a - b; } | |||
| static forcedinline ParallelType mul (ParallelType a, ParallelType b) noexcept { return a * b; } | |||
| static forcedinline ParallelType max (ParallelType a, ParallelType b) noexcept { return jmax (a, b); } | |||
| static forcedinline ParallelType min (ParallelType a, ParallelType b) noexcept { return jmin (a, b); } | |||
| static forcedinline Type max (ParallelType a) noexcept { return a; } | |||
| static forcedinline Type min (ParallelType a) noexcept { return a; } | |||
| }; | |||
| #define JUCE_BEGIN_VEC_OP \ | |||
| typedef FloatVectorHelpers::ModeType<sizeof(*dest)>::Mode Mode; \ | |||
| if (Mode::numParallel > 1) \ | |||
| { \ | |||
| const int numLongOps = num / Mode::numParallel; | |||
| #define JUCE_FINISH_VEC_OP(normalOp) \ | |||
| num &= (Mode::numParallel - 1); \ | |||
| if (num == 0) return; \ | |||
| } \ | |||
| for (int i = 0; i < num; ++i) normalOp; | |||
| #define JUCE_PERFORM_VEC_OP_DEST(normalOp, vecOp, locals, setupOp) \ | |||
| JUCE_BEGIN_VEC_OP \ | |||
| setupOp \ | |||
| JUCE_VEC_LOOP (vecOp, dummy, Mode::loadU, Mode::storeU, locals, JUCE_INCREMENT_DEST) \ | |||
| JUCE_FINISH_VEC_OP (normalOp) | |||
| #define JUCE_PERFORM_VEC_OP_SRC_DEST(normalOp, vecOp, locals, increment, setupOp) \ | |||
| JUCE_BEGIN_VEC_OP \ | |||
| setupOp \ | |||
| JUCE_VEC_LOOP (vecOp, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \ | |||
| JUCE_FINISH_VEC_OP (normalOp) | |||
| #define JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST(normalOp, vecOp, locals, increment, setupOp) \ | |||
| JUCE_BEGIN_VEC_OP \ | |||
| setupOp \ | |||
| JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \ | |||
| JUCE_FINISH_VEC_OP (normalOp) | |||
| //============================================================================== | |||
| #else | |||
| #define JUCE_PERFORM_VEC_OP_DEST(normalOp, vecOp, locals, setupOp) \ | |||
| for (int i = 0; i < num; ++i) normalOp; | |||
| #define JUCE_PERFORM_VEC_OP_SRC_DEST(normalOp, vecOp, locals, increment, setupOp) \ | |||
| for (int i = 0; i < num; ++i) normalOp; | |||
| #define JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST(normalOp, vecOp, locals, increment, setupOp) \ | |||
| for (int i = 0; i < num; ++i) normalOp; | |||
| #endif | |||
| //============================================================================== | |||
| #define JUCE_VEC_LOOP(vecOp, srcLoad, dstLoad, dstStore, locals, increment) \ | |||
| for (int i = 0; i < numLongOps; ++i) \ | |||
| { \ | |||
| locals (srcLoad, dstLoad); \ | |||
| dstStore (dest, vecOp); \ | |||
| increment; \ | |||
| } | |||
| #define JUCE_VEC_LOOP_TWO_SOURCES(vecOp, src1Load, src2Load, dstStore, locals, increment) \ | |||
| for (int i = 0; i < numLongOps; ++i) \ | |||
| { \ | |||
| locals (src1Load, src2Load); \ | |||
| dstStore (dest, vecOp); \ | |||
| increment; \ | |||
| } | |||
| #define JUCE_LOAD_NONE(srcLoad, dstLoad) | |||
| #define JUCE_LOAD_DEST(srcLoad, dstLoad) const Mode::ParallelType d = dstLoad (dest); | |||
| #define JUCE_LOAD_SRC(srcLoad, dstLoad) const Mode::ParallelType s = srcLoad (src); | |||
| #define JUCE_LOAD_SRC1_SRC2(src1Load, src2Load) const Mode::ParallelType s1 = src1Load (src1), s2 = src2Load (src2); | |||
| #define JUCE_LOAD_SRC_DEST(srcLoad, dstLoad) const Mode::ParallelType d = dstLoad (dest), s = srcLoad (src); | |||
| #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON | |||
| template<int typeSize> struct ModeType { typedef BasicOps32 Mode; }; | |||
| template<> struct ModeType<8> { typedef BasicOps64 Mode; }; | |||
| template <typename Mode> | |||
| struct MinMax | |||
| { | |||
| typedef typename Mode::Type Type; | |||
| typedef typename Mode::ParallelType ParallelType; | |||
| static Type findMinOrMax (const Type* src, int num, const bool isMinimum) noexcept | |||
| { | |||
| int numLongOps = num / Mode::numParallel; | |||
| #if JUCE_USE_SSE_INTRINSICS | |||
| if (numLongOps > 1 && isSSE2Available()) | |||
| #else | |||
| if (numLongOps > 1) | |||
| #endif | |||
| { | |||
| ParallelType val; | |||
| #if ! JUCE_USE_ARM_NEON | |||
| if (isAligned (src)) | |||
| { | |||
| val = Mode::loadA (src); | |||
| if (isMinimum) | |||
| { | |||
| while (--numLongOps > 0) | |||
| { | |||
| src += Mode::numParallel; | |||
| val = Mode::min (val, Mode::loadA (src)); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| while (--numLongOps > 0) | |||
| { | |||
| src += Mode::numParallel; | |||
| val = Mode::max (val, Mode::loadA (src)); | |||
| } | |||
| } | |||
| } | |||
| else | |||
| #endif | |||
| { | |||
| val = Mode::loadU (src); | |||
| if (isMinimum) | |||
| { | |||
| while (--numLongOps > 0) | |||
| { | |||
| src += Mode::numParallel; | |||
| val = Mode::min (val, Mode::loadU (src)); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| while (--numLongOps > 0) | |||
| { | |||
| src += Mode::numParallel; | |||
| val = Mode::max (val, Mode::loadU (src)); | |||
| } | |||
| } | |||
| } | |||
| Type result = isMinimum ? Mode::min (val) | |||
| : Mode::max (val); | |||
| num &= (Mode::numParallel - 1); | |||
| src += Mode::numParallel; | |||
| for (int i = 0; i < num; ++i) | |||
| result = isMinimum ? jmin (result, src[i]) | |||
| : jmax (result, src[i]); | |||
| return result; | |||
| } | |||
| return isMinimum ? juce::findMinimum (src, num) | |||
| : juce::findMaximum (src, num); | |||
| } | |||
| static Range<Type> findMinAndMax (const Type* src, int num) noexcept | |||
| { | |||
| int numLongOps = num / Mode::numParallel; | |||
| #if JUCE_USE_SSE_INTRINSICS | |||
| if (numLongOps > 1 && isSSE2Available()) | |||
| #else | |||
| if (numLongOps > 1) | |||
| #endif | |||
| { | |||
| ParallelType mn, mx; | |||
| #if ! JUCE_USE_ARM_NEON | |||
| if (isAligned (src)) | |||
| { | |||
| mn = Mode::loadA (src); | |||
| mx = mn; | |||
| while (--numLongOps > 0) | |||
| { | |||
| src += Mode::numParallel; | |||
| const ParallelType v = Mode::loadA (src); | |||
| mn = Mode::min (mn, v); | |||
| mx = Mode::max (mx, v); | |||
| } | |||
| } | |||
| else | |||
| #endif | |||
| { | |||
| mn = Mode::loadU (src); | |||
| mx = mn; | |||
| while (--numLongOps > 0) | |||
| { | |||
| src += Mode::numParallel; | |||
| const ParallelType v = Mode::loadU (src); | |||
| mn = Mode::min (mn, v); | |||
| mx = Mode::max (mx, v); | |||
| } | |||
| } | |||
| Range<Type> result (Mode::min (mn), | |||
| Mode::max (mx)); | |||
| num &= (Mode::numParallel - 1); | |||
| src += Mode::numParallel; | |||
| for (int i = 0; i < num; ++i) | |||
| result = result.getUnionWith (src[i]); | |||
| return result; | |||
| } | |||
| return Range<Type>::findMinAndMax (src, num); | |||
| } | |||
| }; | |||
| #endif | |||
| } | |||
| //============================================================================== | |||
| void JUCE_CALLTYPE FloatVectorOperations::clear (float* dest, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vclr (dest, 1, (size_t) num); | |||
| #else | |||
| zeromem (dest, num * sizeof (float)); | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::clear (double* dest, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vclrD (dest, 1, (size_t) num); | |||
| #else | |||
| zeromem (dest, num * sizeof (double)); | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::fill (float* dest, float valueToFill, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vfill (&valueToFill, dest, 1, (size_t) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_DEST (dest[i] = valueToFill, val, JUCE_LOAD_NONE, | |||
| const Mode::ParallelType val = Mode::load1 (valueToFill);) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::fill (double* dest, double valueToFill, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vfillD (&valueToFill, dest, 1, (size_t) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_DEST (dest[i] = valueToFill, val, JUCE_LOAD_NONE, | |||
| const Mode::ParallelType val = Mode::load1 (valueToFill);) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::copy (float* dest, const float* src, int num) noexcept | |||
| { | |||
| memcpy (dest, src, (size_t) num * sizeof (float)); | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::copy (double* dest, const double* src, int num) noexcept | |||
| { | |||
| memcpy (dest, src, (size_t) num * sizeof (double)); | |||
| } | |||
| 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, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] * multiplier, Mode::mul (mult, s), | |||
| JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | |||
| const Mode::ParallelType mult = Mode::load1 (multiplier);) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::copyWithMultiply (double* dest, const double* src, double multiplier, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vsmulD (src, 1, &multiplier, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] * multiplier, Mode::mul (mult, s), | |||
| JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | |||
| const Mode::ParallelType mult = Mode::load1 (multiplier);) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, float amount, int num) noexcept | |||
| { | |||
| JUCE_PERFORM_VEC_OP_DEST (dest[i] += amount, Mode::add (d, amountToAdd), JUCE_LOAD_DEST, | |||
| const Mode::ParallelType amountToAdd = Mode::load1 (amount);) | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, double amount, int num) noexcept | |||
| { | |||
| JUCE_PERFORM_VEC_OP_DEST (dest[i] += amount, Mode::add (d, amountToAdd), JUCE_LOAD_DEST, | |||
| const Mode::ParallelType amountToAdd = Mode::load1 (amount);) | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, float* src, float amount, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vsadd (src, 1, &amount, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] + amount, Mode::add (am, s), | |||
| JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | |||
| const Mode::ParallelType am = Mode::load1 (amount);) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, double* src, double amount, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vsaddD (src, 1, &amount, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] + amount, Mode::add (am, s), | |||
| JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | |||
| const Mode::ParallelType am = Mode::load1 (amount);) | |||
| #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, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] += src[i], Mode::add (d, s), JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, ) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, const double* src, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vaddD (src, 1, dest, 1, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] += src[i], Mode::add (d, s), JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, ) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, const float* src1, const float* src2, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vadd (src1, 1, src2, 1, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] + src2[i], Mode::add (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, const double* src1, const double* src2, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vaddD (src1, 1, src2, 1, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] + src2[i], Mode::add (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::subtract (float* dest, const float* src, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vsub (src, 1, dest, 1, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] -= src[i], Mode::sub (d, s), JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, ) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::subtract (double* dest, const double* src, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vsubD (src, 1, dest, 1, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] -= src[i], Mode::sub (d, s), JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, ) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::subtract (float* dest, const float* src1, const float* src2, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vsub (src2, 1, src1, 1, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] - src2[i], Mode::sub (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::subtract (double* dest, const double* src1, const double* src2, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vsubD (src2, 1, src1, 1, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] - src2[i], Mode::sub (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::addWithMultiply (float* dest, const float* src, float multiplier, int num) noexcept | |||
| { | |||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] += src[i] * multiplier, Mode::add (d, Mode::mul (mult, s)), | |||
| JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, | |||
| const Mode::ParallelType mult = Mode::load1 (multiplier);) | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::addWithMultiply (double* dest, const double* src, double multiplier, int num) noexcept | |||
| { | |||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] += src[i] * multiplier, Mode::add (d, Mode::mul (mult, s)), | |||
| JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, | |||
| const Mode::ParallelType mult = Mode::load1 (multiplier);) | |||
| } | |||
| 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, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] *= src[i], Mode::mul (d, s), JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, ) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::multiply (double* dest, const double* src, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vmulD (src, 1, dest, 1, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] *= src[i], Mode::mul (d, s), JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, ) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::multiply (float* dest, const float* src1, const float* src2, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vmul (src1, 1, src2, 1, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] * src2[i], Mode::mul (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::multiply (double* dest, const double* src1, const double* src2, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vmulD (src1, 1, src2, 1, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] * src2[i], Mode::mul (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_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, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_DEST (dest[i] *= multiplier, Mode::mul (d, mult), JUCE_LOAD_DEST, | |||
| const Mode::ParallelType mult = Mode::load1 (multiplier);) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::multiply (double* dest, double multiplier, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vsmulD (dest, 1, &multiplier, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_DEST (dest[i] *= multiplier, Mode::mul (d, mult), JUCE_LOAD_DEST, | |||
| const Mode::ParallelType mult = Mode::load1 (multiplier);) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::multiply (float* dest, const float* src, float multiplier, int num) noexcept | |||
| { | |||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] * multiplier, Mode::mul (mult, s), | |||
| JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | |||
| const Mode::ParallelType mult = Mode::load1 (multiplier);) | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::multiply (double* dest, const double* src, double multiplier, int num) noexcept | |||
| { | |||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] * multiplier, Mode::mul (mult, s), | |||
| JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | |||
| const Mode::ParallelType mult = Mode::load1 (multiplier);) | |||
| } | |||
| void FloatVectorOperations::negate (float* dest, const float* src, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vneg ((float*) src, 1, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| copyWithMultiply (dest, src, -1.0f, num); | |||
| #endif | |||
| } | |||
| void FloatVectorOperations::negate (double* dest, const double* src, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vnegD ((double*) src, 1, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| copyWithMultiply (dest, src, -1.0f, num); | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::convertFixedToFloat (float* dest, const int* src, float multiplier, int num) noexcept | |||
| { | |||
| #if JUCE_USE_ARM_NEON | |||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] * multiplier, | |||
| vmulq_n_f32 (vcvtq_f32_s32 (vld1q_s32 (src)), multiplier), | |||
| JUCE_LOAD_NONE, JUCE_INCREMENT_SRC_DEST, ) | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] * multiplier, | |||
| Mode::mul (mult, _mm_cvtepi32_ps (_mm_loadu_si128 ((const __m128i*) src))), | |||
| JUCE_LOAD_NONE, JUCE_INCREMENT_SRC_DEST, | |||
| const Mode::ParallelType mult = Mode::load1 (multiplier);) | |||
| #endif | |||
| } | |||
| Range<float> JUCE_CALLTYPE FloatVectorOperations::findMinAndMax (const float* src, int num) noexcept | |||
| { | |||
| #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON | |||
| return FloatVectorHelpers::MinMax<FloatVectorHelpers::BasicOps32>::findMinAndMax (src, num); | |||
| #else | |||
| return Range<float>::findMinAndMax (src, num); | |||
| #endif | |||
| } | |||
| Range<double> JUCE_CALLTYPE FloatVectorOperations::findMinAndMax (const double* src, int num) noexcept | |||
| { | |||
| #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON | |||
| return FloatVectorHelpers::MinMax<FloatVectorHelpers::BasicOps64>::findMinAndMax (src, num); | |||
| #else | |||
| return Range<double>::findMinAndMax (src, num); | |||
| #endif | |||
| } | |||
| float JUCE_CALLTYPE FloatVectorOperations::findMinimum (const float* src, int num) noexcept | |||
| { | |||
| #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON | |||
| return FloatVectorHelpers::MinMax<FloatVectorHelpers::BasicOps32>::findMinOrMax (src, num, true); | |||
| #else | |||
| return juce::findMinimum (src, num); | |||
| #endif | |||
| } | |||
| double JUCE_CALLTYPE FloatVectorOperations::findMinimum (const double* src, int num) noexcept | |||
| { | |||
| #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON | |||
| return FloatVectorHelpers::MinMax<FloatVectorHelpers::BasicOps64>::findMinOrMax (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 || JUCE_USE_ARM_NEON | |||
| return FloatVectorHelpers::MinMax<FloatVectorHelpers::BasicOps32>::findMinOrMax (src, num, false); | |||
| #else | |||
| return juce::findMaximum (src, num); | |||
| #endif | |||
| } | |||
| double JUCE_CALLTYPE FloatVectorOperations::findMaximum (const double* src, int num) noexcept | |||
| { | |||
| #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON | |||
| return FloatVectorHelpers::MinMax<FloatVectorHelpers::BasicOps64>::findMinOrMax (src, num, false); | |||
| #else | |||
| return juce::findMaximum (src, num); | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::enableFlushToZeroMode (bool shouldEnable) noexcept | |||
| { | |||
| #if JUCE_USE_SSE_INTRINSICS | |||
| if (FloatVectorHelpers::isSSE2Available()) | |||
| _MM_SET_FLUSH_ZERO_MODE (shouldEnable ? _MM_FLUSH_ZERO_ON : _MM_FLUSH_ZERO_OFF); | |||
| #endif | |||
| (void) shouldEnable; | |||
| } | |||
| //============================================================================== | |||
| //============================================================================== | |||
| #if JUCE_UNIT_TESTS | |||
| class FloatVectorOperationsTests : public UnitTest | |||
| { | |||
| public: | |||
| FloatVectorOperationsTests() : UnitTest ("FloatVectorOperations") {} | |||
| template <typename ValueType> | |||
| struct TestRunner | |||
| { | |||
| static void runTest (UnitTest& u, Random random) | |||
| { | |||
| const int range = random.nextBool() ? 500 : 10; | |||
| const int num = random.nextInt (range) + 1; | |||
| HeapBlock<ValueType> buffer1 ((size_t) num + 16), buffer2 ((size_t) num + 16); | |||
| HeapBlock<int> buffer3 ((size_t) num + 16); | |||
| #if JUCE_ARM | |||
| ValueType* const data1 = buffer1; | |||
| ValueType* const data2 = buffer2; | |||
| int* const int1 = buffer3; | |||
| #else | |||
| ValueType* const data1 = addBytesToPointer (buffer1.getData(), random.nextInt (16)); | |||
| ValueType* const data2 = addBytesToPointer (buffer2.getData(), random.nextInt (16)); | |||
| int* const int1 = addBytesToPointer (buffer3.getData(), random.nextInt (16)); | |||
| #endif | |||
| fillRandomly (random, data1, num); | |||
| fillRandomly (random, data2, num); | |||
| Range<ValueType> minMax1 (FloatVectorOperations::findMinAndMax (data1, num)); | |||
| Range<ValueType> minMax2 (Range<ValueType>::findMinAndMax (data1, num)); | |||
| u.expect (minMax1 == minMax2); | |||
| u.expect (valuesMatch (FloatVectorOperations::findMinimum (data1, num), juce::findMinimum (data1, num))); | |||
| u.expect (valuesMatch (FloatVectorOperations::findMaximum (data1, num), juce::findMaximum (data1, num))); | |||
| u.expect (valuesMatch (FloatVectorOperations::findMinimum (data2, num), juce::findMinimum (data2, num))); | |||
| u.expect (valuesMatch (FloatVectorOperations::findMaximum (data2, num), juce::findMaximum (data2, num))); | |||
| FloatVectorOperations::clear (data1, num); | |||
| u.expect (areAllValuesEqual (data1, num, 0)); | |||
| FloatVectorOperations::fill (data1, (ValueType) 2, num); | |||
| u.expect (areAllValuesEqual (data1, num, (ValueType) 2)); | |||
| FloatVectorOperations::add (data1, (ValueType) 2, num); | |||
| u.expect (areAllValuesEqual (data1, num, (ValueType) 4)); | |||
| FloatVectorOperations::copy (data2, data1, num); | |||
| u.expect (areAllValuesEqual (data2, num, (ValueType) 4)); | |||
| FloatVectorOperations::add (data2, data1, num); | |||
| u.expect (areAllValuesEqual (data2, num, (ValueType) 8)); | |||
| FloatVectorOperations::copyWithMultiply (data2, data1, (ValueType) 4, num); | |||
| u.expect (areAllValuesEqual (data2, num, (ValueType) 16)); | |||
| FloatVectorOperations::addWithMultiply (data2, data1, (ValueType) 4, num); | |||
| u.expect (areAllValuesEqual (data2, num, (ValueType) 32)); | |||
| FloatVectorOperations::multiply (data1, (ValueType) 2, num); | |||
| u.expect (areAllValuesEqual (data1, num, (ValueType) 8)); | |||
| FloatVectorOperations::multiply (data1, data2, num); | |||
| u.expect (areAllValuesEqual (data1, num, (ValueType) 256)); | |||
| FloatVectorOperations::negate (data2, data1, num); | |||
| u.expect (areAllValuesEqual (data2, num, (ValueType) -256)); | |||
| FloatVectorOperations::subtract (data1, data2, num); | |||
| u.expect (areAllValuesEqual (data1, num, (ValueType) 512)); | |||
| fillRandomly (random, int1, num); | |||
| doConversionTest (u, data1, data2, int1, num); | |||
| } | |||
| static void doConversionTest (UnitTest& u, float* data1, float* data2, int* const int1, int num) | |||
| { | |||
| FloatVectorOperations::convertFixedToFloat (data1, int1, 2.0f, num); | |||
| convertFixed (data2, int1, 2.0f, num); | |||
| u.expect (buffersMatch (data1, data2, num)); | |||
| } | |||
| static void doConversionTest (UnitTest&, double*, double*, int*, int) {} | |||
| static void fillRandomly (Random& random, ValueType* d, int num) | |||
| { | |||
| while (--num >= 0) | |||
| *d++ = (ValueType) (random.nextDouble() * 1000.0); | |||
| } | |||
| static void fillRandomly (Random& random, int* d, int num) | |||
| { | |||
| while (--num >= 0) | |||
| *d++ = random.nextInt(); | |||
| } | |||
| static void convertFixed (float* d, const int* s, ValueType multiplier, int num) | |||
| { | |||
| while (--num >= 0) | |||
| *d++ = *s++ * multiplier; | |||
| } | |||
| static bool areAllValuesEqual (const ValueType* d, int num, ValueType target) | |||
| { | |||
| while (--num >= 0) | |||
| if (*d++ != target) | |||
| return false; | |||
| return true; | |||
| } | |||
| static bool buffersMatch (const ValueType* d1, const ValueType* d2, int num) | |||
| { | |||
| while (--num >= 0) | |||
| if (! valuesMatch (*d1++, *d2++)) | |||
| return false; | |||
| return true; | |||
| } | |||
| static bool valuesMatch (ValueType v1, ValueType v2) | |||
| { | |||
| return std::abs (v1 - v2) < std::numeric_limits<ValueType>::epsilon(); | |||
| } | |||
| }; | |||
| void runTest() | |||
| { | |||
| beginTest ("FloatVectorOperations"); | |||
| for (int i = 1000; --i >= 0;) | |||
| { | |||
| TestRunner<float>::runTest (*this, getRandom()); | |||
| TestRunner<double>::runTest (*this, getRandom()); | |||
| } | |||
| } | |||
| }; | |||
| static FloatVectorOperationsTests vectorOpTests; | |||
| #endif | |||
| @@ -1,162 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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; | |||
| /** Clears a vector of doubles. */ | |||
| static void JUCE_CALLTYPE clear (double* 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 repeated value into a vector of doubles. */ | |||
| static void JUCE_CALLTYPE fill (double* dest, double 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 doubles. */ | |||
| static void JUCE_CALLTYPE copy (double* dest, const double* 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; | |||
| /** Copies a vector of doubles, multiplying each value by a given multiplier */ | |||
| static void JUCE_CALLTYPE copyWithMultiply (double* dest, const double* src, double multiplier, int numValues) noexcept; | |||
| /** Adds a fixed value to the destination values. */ | |||
| static void JUCE_CALLTYPE add (float* dest, float amountToAdd, int numValues) noexcept; | |||
| /** Adds a fixed value to the destination values. */ | |||
| static void JUCE_CALLTYPE add (double* dest, double amountToAdd, int numValues) noexcept; | |||
| /** Adds a fixed value to each source value and stores it in the destination array. */ | |||
| static void JUCE_CALLTYPE add (float* dest, float* src, float amount, int numValues) noexcept; | |||
| /** Adds a fixed value to each source value and stores it in the destination array. */ | |||
| static void JUCE_CALLTYPE add (double* dest, double* src, double amount, 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 the source values to the destination values. */ | |||
| static void JUCE_CALLTYPE add (double* dest, const double* src, int numValues) noexcept; | |||
| /** Adds each source1 value to the corresponding source2 value and stores the result in the destination array. */ | |||
| static void JUCE_CALLTYPE add (float* dest, const float* src1, const float* src2, int num) noexcept; | |||
| /** Adds each source1 value to the corresponding source2 value and stores the result in the destination array. */ | |||
| static void JUCE_CALLTYPE add (double* dest, const double* src1, const double* src2, int num) noexcept; | |||
| /** Subtracts the source values from the destination values. */ | |||
| static void JUCE_CALLTYPE subtract (float* dest, const float* src, int numValues) noexcept; | |||
| /** Subtracts the source values from the destination values. */ | |||
| static void JUCE_CALLTYPE subtract (double* dest, const double* src, int numValues) noexcept; | |||
| /** Subtracts each source2 value from the corresponding source1 value and stores the result in the destination array. */ | |||
| static void JUCE_CALLTYPE subtract (float* dest, const float* src1, const float* src2, int num) noexcept; | |||
| /** Subtracts each source2 value from the corresponding source1 value and stores the result in the destination array. */ | |||
| static void JUCE_CALLTYPE subtract (double* dest, const double* src1, const double* src2, int num) 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 each source value by the given multiplier, then adds it to the destination value. */ | |||
| static void JUCE_CALLTYPE addWithMultiply (double* dest, const double* src, double 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 the destination values by the source values. */ | |||
| static void JUCE_CALLTYPE multiply (double* dest, const double* src, int numValues) noexcept; | |||
| /** Multiplies each source1 value by the correspinding source2 value, then stores it in the destination array. */ | |||
| static void JUCE_CALLTYPE multiply (float* dest, const float* src1, const float* src2, int numValues) noexcept; | |||
| /** Multiplies each source1 value by the correspinding source2 value, then stores it in the destination array. */ | |||
| static void JUCE_CALLTYPE multiply (double* dest, const double* src1, const double* src2, 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; | |||
| /** Multiplies each of the destination values by a fixed multiplier. */ | |||
| static void JUCE_CALLTYPE multiply (double* dest, double multiplier, int numValues) noexcept; | |||
| /** Multiplies each of the source values by a fixed multiplier and stores the result in the destination array. */ | |||
| static void JUCE_CALLTYPE multiply (float* dest, const float* src, float multiplier, int num) noexcept; | |||
| /** Multiplies each of the source values by a fixed multiplier and stores the result in the destination array. */ | |||
| static void JUCE_CALLTYPE multiply (double* dest, const double* src, double multiplier, int num) noexcept; | |||
| /** Copies a source vector to a destination, negating each value. */ | |||
| static void JUCE_CALLTYPE negate (float* dest, const float* src, int numValues) noexcept; | |||
| /** Copies a source vector to a destination, negating each value. */ | |||
| static void JUCE_CALLTYPE negate (double* dest, const double* src, 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 Range<float> JUCE_CALLTYPE findMinAndMax (const float* src, int numValues) noexcept; | |||
| /** Finds the miniumum and maximum values in the given array. */ | |||
| static Range<double> JUCE_CALLTYPE findMinAndMax (const double* src, int numValues) noexcept; | |||
| /** Finds the miniumum value in the given array. */ | |||
| static float JUCE_CALLTYPE findMinimum (const float* src, int numValues) noexcept; | |||
| /** Finds the miniumum value in the given array. */ | |||
| static double JUCE_CALLTYPE findMinimum (const double* src, int numValues) noexcept; | |||
| /** Finds the maximum value in the given array. */ | |||
| static float JUCE_CALLTYPE findMaximum (const float* src, int numValues) noexcept; | |||
| /** Finds the maximum value in the given array. */ | |||
| static double JUCE_CALLTYPE findMaximum (const double* src, int numValues) noexcept; | |||
| /** On Intel CPUs, this method enables or disables the SSE flush-to-zero mode. | |||
| Effectively, this is a wrapper around a call to _MM_SET_FLUSH_ZERO_MODE | |||
| */ | |||
| static void JUCE_CALLTYPE enableFlushToZeroMode (bool shouldEnable) noexcept; | |||
| }; | |||
| #endif // JUCE_FLOATVECTOROPERATIONS_H_INCLUDED | |||
| @@ -1,104 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 ? std::pow ((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 | |||
| @@ -1,244 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 (coefficients, sizeof (coefficients)); | |||
| } | |||
| IIRCoefficients::~IIRCoefficients() noexcept {} | |||
| IIRCoefficients::IIRCoefficients (const IIRCoefficients& other) noexcept | |||
| { | |||
| memcpy (coefficients, other.coefficients, sizeof (coefficients)); | |||
| } | |||
| IIRCoefficients& IIRCoefficients::operator= (const IIRCoefficients& other) noexcept | |||
| { | |||
| memcpy (coefficients, other.coefficients, sizeof (coefficients)); | |||
| return *this; | |||
| } | |||
| IIRCoefficients::IIRCoefficients (double c1, double c2, double c3, | |||
| double c4, double c5, double c6) noexcept | |||
| { | |||
| const double a = 1.0 / c4; | |||
| coefficients[0] = (float) (c1 * a); | |||
| coefficients[1] = (float) (c2 * a); | |||
| coefficients[2] = (float) (c3 * a); | |||
| coefficients[3] = (float) (c5 * a); | |||
| coefficients[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.0, | |||
| 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.0, | |||
| 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.coefficients[0] * in + v1; | |||
| JUCE_SNAP_TO_ZERO (out); | |||
| v1 = coefficients.coefficients[1] * in - coefficients.coefficients[3] * out + v2; | |||
| v2 = coefficients.coefficients[2] * in - coefficients.coefficients[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.coefficients[0]; | |||
| const float c1 = coefficients.coefficients[1]; | |||
| const float c2 = coefficients.coefficients[2]; | |||
| const float c3 = coefficients.coefficients[3]; | |||
| const float c4 = coefficients.coefficients[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 | |||
| @@ -1,174 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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; | |||
| //============================================================================== | |||
| /** The raw coefficients. | |||
| You should leave these numbers alone unless you really know what you're doing. | |||
| */ | |||
| float coefficients[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 | |||
| @@ -1,200 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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, (size_t) 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); | |||
| } | |||
| @@ -1,94 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 | |||
| @@ -1,323 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 + samples[i] * 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 | |||
| @@ -1,94 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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" | |||
| #if JUCE_MINGW && ! defined (__SSE2__) | |||
| #define JUCE_USE_SSE_INTRINSICS 0 | |||
| #endif | |||
| #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 | |||
| #ifndef JUCE_USE_VDSP_FRAMEWORK | |||
| #define JUCE_USE_VDSP_FRAMEWORK 1 | |||
| #endif | |||
| #if (JUCE_MAC || JUCE_IOS) && JUCE_USE_VDSP_FRAMEWORK | |||
| #define Point CarbonDummyPointName // (workaround to avoid definition of "Point" by old Carbon headers) | |||
| #include <Accelerate/Accelerate.h> | |||
| #undef Point | |||
| #else | |||
| #undef JUCE_USE_VDSP_FRAMEWORK | |||
| #endif | |||
| #if __ARM_NEON__ && ! (JUCE_USE_VDSP_FRAMEWORK || defined (JUCE_USE_ARM_NEON)) | |||
| #define JUCE_USE_ARM_NEON 1 | |||
| #include <arm_neon.h> | |||
| #endif | |||
| namespace juce | |||
| { | |||
| #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" | |||
| } | |||
| @@ -1,59 +1,5 @@ | |||
| /* | |||
| ============================================================================== | |||
| // This is an auto-generated file to redirect any included | |||
| // module headers to the correct external folder. | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2013 - Raw Material Software Ltd. | |||
| #include "../../../../../modules/juce_audio_basics/juce_audio_basics.h" | |||
| 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 | |||
| { | |||
| #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_MidiMessage.h" | |||
| #include "midi/juce_MidiBuffer.h" | |||
| #include "midi/juce_MidiMessageSequence.h" | |||
| #include "midi/juce_MidiFile.h" | |||
| #include "midi/juce_MidiKeyboardState.h" | |||
| #include "sources/juce_AudioSource.h" | |||
| #include "sources/juce_PositionableAudioSource.h" | |||
| #include "sources/juce_BufferingAudioSource.h" | |||
| #include "sources/juce_ChannelRemappingAudioSource.h" | |||
| #include "sources/juce_IIRFilterAudioSource.h" | |||
| #include "sources/juce_MixerAudioSource.h" | |||
| #include "sources/juce_ResamplingAudioSource.h" | |||
| #include "sources/juce_ReverbAudioSource.h" | |||
| #include "sources/juce_ToneGeneratorAudioSource.h" | |||
| #include "synthesisers/juce_Synthesiser.h" | |||
| } | |||
| #endif // JUCE_AUDIO_BASICS_H_INCLUDED | |||
| @@ -1,25 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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" | |||
| @@ -1,24 +0,0 @@ | |||
| { | |||
| "id": "juce_audio_basics", | |||
| "name": "JUCE audio and midi data classes", | |||
| "version": "3.0.8", | |||
| "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" | |||
| } | |||
| @@ -1,229 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 int32*> (d); | |||
| } | |||
| inline uint16 getEventDataSize (const void* const d) noexcept | |||
| { | |||
| return *reinterpret_cast<const uint16*> (static_cast<const char*> (d) + sizeof (int32)); | |||
| } | |||
| inline uint16 getEventTotalSize (const void* const d) noexcept | |||
| { | |||
| return getEventDataSize (d) + sizeof (int32) + sizeof (uint16); | |||
| } | |||
| static int findActualEventLength (const uint8* const data, const int maxBytes) noexcept | |||
| { | |||
| unsigned int byte = (unsigned int) *data; | |||
| int size = 0; | |||
| if (byte == 0xf0 || byte == 0xf7) | |||
| { | |||
| const uint8* d = data + 1; | |||
| while (d < data + maxBytes) | |||
| if (*d++ == 0xf7) | |||
| break; | |||
| size = (int) (d - data); | |||
| } | |||
| else if (byte == 0xff) | |||
| { | |||
| int n; | |||
| const int bytesLeft = MidiMessage::readVariableLengthVal (data + 1, n); | |||
| size = jmin (maxBytes, n + 2 + bytesLeft); | |||
| } | |||
| else if (byte >= 0x80) | |||
| { | |||
| size = jmin (maxBytes, MidiMessage::getMessageLengthFromFirstByte ((uint8) byte)); | |||
| } | |||
| return size; | |||
| } | |||
| static uint8* findEventAfter (uint8* d, uint8* endData, const int samplePosition) noexcept | |||
| { | |||
| while (d < endData && getEventTime (d) <= samplePosition) | |||
| d += getEventTotalSize (d); | |||
| return d; | |||
| } | |||
| } | |||
| //============================================================================== | |||
| MidiBuffer::MidiBuffer() noexcept {} | |||
| MidiBuffer::~MidiBuffer() {} | |||
| MidiBuffer::MidiBuffer (const MidiBuffer& other) noexcept : data (other.data) {} | |||
| MidiBuffer& MidiBuffer::operator= (const MidiBuffer& other) noexcept | |||
| { | |||
| data = other.data; | |||
| return *this; | |||
| } | |||
| MidiBuffer::MidiBuffer (const MidiMessage& message) noexcept | |||
| { | |||
| addEvent (message, 0); | |||
| } | |||
| void MidiBuffer::swapWith (MidiBuffer& other) noexcept { data.swapWith (other.data); } | |||
| void MidiBuffer::clear() noexcept { data.clearQuick(); } | |||
| void MidiBuffer::ensureSize (size_t minimumNumBytes) { data.ensureStorageAllocated ((int) minimumNumBytes); } | |||
| bool MidiBuffer::isEmpty() const noexcept { return data.size() == 0; } | |||
| void MidiBuffer::clear (const int startSample, const int numSamples) | |||
| { | |||
| uint8* const start = MidiBufferHelpers::findEventAfter (data.begin(), data.end(), startSample - 1); | |||
| uint8* const end = MidiBufferHelpers::findEventAfter (start, data.end(), startSample + numSamples - 1); | |||
| data.removeRange ((int) (start - data.begin()), (int) (end - data.begin())); | |||
| } | |||
| void MidiBuffer::addEvent (const MidiMessage& m, const int sampleNumber) | |||
| { | |||
| addEvent (m.getRawData(), m.getRawDataSize(), sampleNumber); | |||
| } | |||
| void MidiBuffer::addEvent (const void* const newData, const int maxBytes, const int sampleNumber) | |||
| { | |||
| const int numBytes = MidiBufferHelpers::findActualEventLength (static_cast<const uint8*> (newData), maxBytes); | |||
| if (numBytes > 0) | |||
| { | |||
| const size_t newItemSize = (size_t) numBytes + sizeof (int32) + sizeof (uint16); | |||
| const int offset = (int) (MidiBufferHelpers::findEventAfter (data.begin(), data.end(), sampleNumber) - data.begin()); | |||
| data.insertMultiple (offset, 0, (int) newItemSize); | |||
| uint8* const d = data.begin() + offset; | |||
| *reinterpret_cast<int32*> (d) = sampleNumber; | |||
| *reinterpret_cast<uint16*> (d + 4) = (uint16) numBytes; | |||
| memcpy (d + 6, newData, (size_t) numBytes); | |||
| } | |||
| } | |||
| void MidiBuffer::addEvents (const MidiBuffer& otherBuffer, | |||
| const int startSample, | |||
| const int numSamples, | |||
| const int sampleDeltaToAdd) | |||
| { | |||
| Iterator i (otherBuffer); | |||
| i.setNextSamplePosition (startSample); | |||
| const uint8* eventData; | |||
| int eventSize, position; | |||
| while (i.getNextEvent (eventData, eventSize, position) | |||
| && (position < startSample + numSamples || numSamples < 0)) | |||
| { | |||
| addEvent (eventData, eventSize, position + sampleDeltaToAdd); | |||
| } | |||
| } | |||
| int MidiBuffer::getNumEvents() const noexcept | |||
| { | |||
| int n = 0; | |||
| const uint8* const end = data.end(); | |||
| for (const uint8* d = data.begin(); d < end; ++n) | |||
| d += MidiBufferHelpers::getEventTotalSize (d); | |||
| return n; | |||
| } | |||
| int MidiBuffer::getFirstEventTime() const noexcept | |||
| { | |||
| return data.size() > 0 ? MidiBufferHelpers::getEventTime (data.begin()) : 0; | |||
| } | |||
| int MidiBuffer::getLastEventTime() const noexcept | |||
| { | |||
| if (data.size() == 0) | |||
| return 0; | |||
| const uint8* const endData = data.end(); | |||
| for (const uint8* d = data.begin();;) | |||
| { | |||
| const uint8* const nextOne = d + MidiBufferHelpers::getEventTotalSize (d); | |||
| if (nextOne >= endData) | |||
| return MidiBufferHelpers::getEventTime (d); | |||
| d = nextOne; | |||
| } | |||
| } | |||
| //============================================================================== | |||
| MidiBuffer::Iterator::Iterator (const MidiBuffer& b) noexcept | |||
| : buffer (b), data (b.data.begin()) | |||
| { | |||
| } | |||
| MidiBuffer::Iterator::~Iterator() noexcept | |||
| { | |||
| } | |||
| void MidiBuffer::Iterator::setNextSamplePosition (const int samplePosition) noexcept | |||
| { | |||
| data = buffer.data.begin(); | |||
| const uint8* const dataEnd = buffer.data.end(); | |||
| while (data < dataEnd && MidiBufferHelpers::getEventTime (data) < samplePosition) | |||
| data += MidiBufferHelpers::getEventTotalSize (data); | |||
| } | |||
| bool MidiBuffer::Iterator::getNextEvent (const uint8* &midiData, int& numBytes, int& samplePosition) noexcept | |||
| { | |||
| if (data >= buffer.data.end()) | |||
| return false; | |||
| samplePosition = MidiBufferHelpers::getEventTime (data); | |||
| const int itemSize = MidiBufferHelpers::getEventDataSize (data); | |||
| numBytes = itemSize; | |||
| midiData = data + sizeof (int32) + sizeof (uint16); | |||
| data += sizeof (int32) + sizeof (uint16) + (size_t) itemSize; | |||
| return true; | |||
| } | |||
| bool MidiBuffer::Iterator::getNextEvent (MidiMessage& result, int& samplePosition) noexcept | |||
| { | |||
| if (data >= buffer.data.end()) | |||
| return false; | |||
| samplePosition = MidiBufferHelpers::getEventTime (data); | |||
| const int itemSize = MidiBufferHelpers::getEventDataSize (data); | |||
| result = MidiMessage (data + sizeof (int32) + sizeof (uint16), itemSize, samplePosition); | |||
| data += sizeof (int32) + sizeof (uint16) + (size_t) itemSize; | |||
| return true; | |||
| } | |||
| @@ -1,235 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 | |||
| //============================================================================== | |||
| /** | |||
| Holds a sequence of time-stamped midi events. | |||
| Analogous to the AudioSampleBuffer, this holds a set of midi events with | |||
| integer time-stamps. The buffer is kept sorted in order of the time-stamps. | |||
| If you're working with a sequence of midi events that may need to be manipulated | |||
| or read/written to a midi file, then MidiMessageSequence is probably a more | |||
| appropriate container. MidiBuffer is designed for lower-level streams of raw | |||
| midi data. | |||
| @see MidiMessage | |||
| */ | |||
| class JUCE_API MidiBuffer | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates an empty MidiBuffer. */ | |||
| MidiBuffer() noexcept; | |||
| /** Creates a MidiBuffer containing a single midi message. */ | |||
| explicit MidiBuffer (const MidiMessage& message) noexcept; | |||
| /** Creates a copy of another MidiBuffer. */ | |||
| MidiBuffer (const MidiBuffer&) noexcept; | |||
| /** Makes a copy of another MidiBuffer. */ | |||
| MidiBuffer& operator= (const MidiBuffer&) noexcept; | |||
| /** Destructor */ | |||
| ~MidiBuffer(); | |||
| //============================================================================== | |||
| /** Removes all events from the buffer. */ | |||
| void clear() noexcept; | |||
| /** Removes all events between two times from the buffer. | |||
| All events for which (start <= event position < start + numSamples) will | |||
| be removed. | |||
| */ | |||
| void clear (int start, int numSamples); | |||
| /** Returns true if the buffer is empty. | |||
| To actually retrieve the events, use a MidiBuffer::Iterator object | |||
| */ | |||
| bool isEmpty() const noexcept; | |||
| /** Counts the number of events in the buffer. | |||
| This is actually quite a slow operation, as it has to iterate through all | |||
| the events, so you might prefer to call isEmpty() if that's all you need | |||
| to know. | |||
| */ | |||
| int getNumEvents() const noexcept; | |||
| /** Adds an event to the buffer. | |||
| The sample number will be used to determine the position of the event in | |||
| the buffer, which is always kept sorted. The MidiMessage's timestamp is | |||
| ignored. | |||
| If an event is added whose sample position is the same as one or more events | |||
| already in the buffer, the new event will be placed after the existing ones. | |||
| To retrieve events, use a MidiBuffer::Iterator object | |||
| */ | |||
| void addEvent (const MidiMessage& midiMessage, int sampleNumber); | |||
| /** Adds an event to the buffer from raw midi data. | |||
| The sample number will be used to determine the position of the event in | |||
| the buffer, which is always kept sorted. | |||
| If an event is added whose sample position is the same as one or more events | |||
| already in the buffer, the new event will be placed after the existing ones. | |||
| The event data will be inspected to calculate the number of bytes in length that | |||
| the midi event really takes up, so maxBytesOfMidiData may be longer than the data | |||
| that actually gets stored. E.g. if you pass in a note-on and a length of 4 bytes, | |||
| it'll actually only store 3 bytes. If the midi data is invalid, it might not | |||
| add an event at all. | |||
| To retrieve events, use a MidiBuffer::Iterator object | |||
| */ | |||
| void addEvent (const void* rawMidiData, | |||
| int maxBytesOfMidiData, | |||
| int sampleNumber); | |||
| /** Adds some events from another buffer to this one. | |||
| @param otherBuffer the buffer containing the events you want to add | |||
| @param startSample the lowest sample number in the source buffer for which | |||
| events should be added. Any source events whose timestamp is | |||
| less than this will be ignored | |||
| @param numSamples the valid range of samples from the source buffer for which | |||
| events should be added - i.e. events in the source buffer whose | |||
| timestamp is greater than or equal to (startSample + numSamples) | |||
| will be ignored. If this value is less than 0, all events after | |||
| startSample will be taken. | |||
| @param sampleDeltaToAdd a value which will be added to the source timestamps of the events | |||
| that are added to this buffer | |||
| */ | |||
| void addEvents (const MidiBuffer& otherBuffer, | |||
| int startSample, | |||
| int numSamples, | |||
| int sampleDeltaToAdd); | |||
| /** Returns the sample number of the first event in the buffer. | |||
| If the buffer's empty, this will just return 0. | |||
| */ | |||
| int getFirstEventTime() const noexcept; | |||
| /** Returns the sample number of the last event in the buffer. | |||
| If the buffer's empty, this will just return 0. | |||
| */ | |||
| int getLastEventTime() const noexcept; | |||
| //============================================================================== | |||
| /** Exchanges the contents of this buffer with another one. | |||
| This is a quick operation, because no memory allocating or copying is done, it | |||
| just swaps the internal state of the two buffers. | |||
| */ | |||
| void swapWith (MidiBuffer&) noexcept; | |||
| /** Preallocates some memory for the buffer to use. | |||
| This helps to avoid needing to reallocate space when the buffer has messages | |||
| added to it. | |||
| */ | |||
| void ensureSize (size_t minimumNumBytes); | |||
| //============================================================================== | |||
| /** | |||
| Used to iterate through the events in a MidiBuffer. | |||
| Note that altering the buffer while an iterator is using it isn't a | |||
| safe operation. | |||
| @see MidiBuffer | |||
| */ | |||
| class JUCE_API Iterator | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates an Iterator for this MidiBuffer. */ | |||
| Iterator (const MidiBuffer&) noexcept; | |||
| /** Destructor. */ | |||
| ~Iterator() noexcept; | |||
| //============================================================================== | |||
| /** Repositions the iterator so that the next event retrieved will be the first | |||
| one whose sample position is at greater than or equal to the given position. | |||
| */ | |||
| void setNextSamplePosition (int samplePosition) noexcept; | |||
| /** Retrieves a copy of the next event from the buffer. | |||
| @param result on return, this will be the message. The MidiMessage's timestamp | |||
| is set to the same value as samplePosition. | |||
| @param samplePosition on return, this will be the position of the event, as a | |||
| sample index in the buffer | |||
| @returns true if an event was found, or false if the iterator has reached | |||
| the end of the buffer | |||
| */ | |||
| bool getNextEvent (MidiMessage& result, | |||
| int& samplePosition) noexcept; | |||
| /** Retrieves the next event from the buffer. | |||
| @param midiData on return, this pointer will be set to a block of data containing | |||
| the midi message. Note that to make it fast, this is a pointer | |||
| directly into the MidiBuffer's internal data, so is only valid | |||
| temporarily until the MidiBuffer is altered. | |||
| @param numBytesOfMidiData on return, this is the number of bytes of data used by the | |||
| midi message | |||
| @param samplePosition on return, this will be the position of the event, as a | |||
| sample index in the buffer | |||
| @returns true if an event was found, or false if the iterator has reached | |||
| the end of the buffer | |||
| */ | |||
| bool getNextEvent (const uint8* &midiData, | |||
| int& numBytesOfMidiData, | |||
| int& samplePosition) noexcept; | |||
| private: | |||
| //============================================================================== | |||
| const MidiBuffer& buffer; | |||
| const uint8* data; | |||
| JUCE_DECLARE_NON_COPYABLE (Iterator) | |||
| }; | |||
| /** The raw data holding this buffer. | |||
| Obviously access to this data is provided at your own risk. Its internal format could | |||
| change in future, so don't write code that relies on it! | |||
| */ | |||
| Array<uint8> data; | |||
| private: | |||
| JUCE_LEAK_DETECTOR (MidiBuffer) | |||
| }; | |||
| #endif // JUCE_MIDIBUFFER_H_INCLUDED | |||
| @@ -1,428 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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; | |||
| } | |||
| }; | |||
| template <typename MethodType> | |||
| static void findAllMatchingEvents (const OwnedArray<MidiMessageSequence>& tracks, | |||
| MidiMessageSequence& results, | |||
| MethodType method) | |||
| { | |||
| for (int i = 0; i < tracks.size(); ++i) | |||
| { | |||
| const MidiMessageSequence& track = *tracks.getUnchecked(i); | |||
| const int numEvents = track.getNumEvents(); | |||
| for (int j = 0; j < numEvents; ++j) | |||
| { | |||
| const MidiMessage& m = track.getEventPointer(j)->message; | |||
| if ((m.*method)()) | |||
| results.addEvent (m); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| //============================================================================== | |||
| 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& results) const | |||
| { | |||
| MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isTempoMetaEvent); | |||
| } | |||
| void MidiFile::findAllTimeSigEvents (MidiMessageSequence& results) const | |||
| { | |||
| MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isTimeSignatureMetaEvent); | |||
| } | |||
| void MidiFile::findAllKeySigEvents (MidiMessageSequence& results) const | |||
| { | |||
| MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isKeySignatureMetaEvent); | |||
| } | |||
| 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, int midiFileType) | |||
| { | |||
| jassert (midiFileType >= 0 && midiFileType <= 2); | |||
| out.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MThd")); | |||
| out.writeIntBigEndian (6); | |||
| out.writeShortBigEndian ((short) midiFileType); | |||
| 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; | |||
| bool endOfTrackEventWritten = false; | |||
| for (int i = 0; i < ms.getNumEvents(); ++i) | |||
| { | |||
| const MidiMessage& mm = ms.getEventPointer(i)->message; | |||
| if (mm.isEndOfTrackMetaEvent()) | |||
| endOfTrackEventWritten = true; | |||
| 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; | |||
| } | |||
| if (! endOfTrackEventWritten) | |||
| { | |||
| 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; | |||
| } | |||
| @@ -1,180 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 | |||
| //============================================================================== | |||
| /** | |||
| 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; | |||
| /** Makes a list of all the time-signature meta-events from all tracks in the midi file. | |||
| @param keySigEvents a list to which all the events will be added | |||
| */ | |||
| void findAllKeySigEvents (MidiMessageSequence& keySigEvents) 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. | |||
| The midiFileType value is written as the file's format type, which can be 0, 1 | |||
| or 2 - see the midi file spec for more info about that. | |||
| @returns true if the operation succeeded. | |||
| */ | |||
| bool writeTo (OutputStream& destStream, int midiFileType = 1); | |||
| /** 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*, int size); | |||
| void writeTrack (OutputStream&, int trackNum); | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiFile) | |||
| }; | |||
| #endif // JUCE_MIDIFILE_H_INCLUDED | |||
| @@ -1,183 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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); | |||
| } | |||
| @@ -1,205 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 | |||
| 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 | |||
| @@ -1,903 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 | |||
| @param sysexHasEmbeddedLength when reading sysexes, this flag indicates whether | |||
| to expect the data to begin with a variable-length field | |||
| indicating its size | |||
| */ | |||
| MidiMessage (const void* data, int maxBytesToUse, | |||
| int& numBytesUsed, uint8 lastStatusByte, | |||
| double timeStamp = 0, | |||
| bool sysexHasEmbeddedLength = true); | |||
| /** Creates an active-sense message. | |||
| Since the MidiMessage has to contain a valid message, this default constructor | |||
| just initialises it with an empty sysex message. | |||
| */ | |||
| MidiMessage() noexcept; | |||
| /** Creates a copy of another midi message. */ | |||
| MidiMessage (const MidiMessage&); | |||
| /** Creates a copy of another midi message, with a different timestamp. */ | |||
| MidiMessage (const MidiMessage&, double newTimeStamp); | |||
| /** Destructor. */ | |||
| ~MidiMessage(); | |||
| /** Copies this message from another one. */ | |||
| MidiMessage& operator= (const MidiMessage& other); | |||
| #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
| MidiMessage (MidiMessage&&) noexcept; | |||
| MidiMessage& operator= (MidiMessage&&) noexcept; | |||
| #endif | |||
| //============================================================================== | |||
| /** Returns a pointer to the raw midi data. | |||
| @see getRawDataSize | |||
| */ | |||
| const uint8* getRawData() const noexcept { return allocatedData != nullptr ? allocatedData.getData() : preallocatedData.asBytes; } | |||
| /** 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 is undefined. | |||
| @see isProgramChange, getGMInstrumentName | |||
| */ | |||
| int getProgramChangeNumber() const noexcept; | |||
| /** Creates a program-change message. | |||
| @param channel the midi channel, in the range 1 to 16 | |||
| @param programNumber the midi program number, 0 to 127 | |||
| @see isProgramChange, getGMInstrumentName | |||
| */ | |||
| static MidiMessage programChange (int channel, int programNumber) noexcept; | |||
| //============================================================================== | |||
| /** Returns true if the message is a pitch-wheel move. | |||
| @see getPitchWheelValue, pitchWheel | |||
| */ | |||
| bool isPitchWheel() const noexcept; | |||
| /** Returns the pitch wheel position from a pitch-wheel move message. | |||
| The value returned is a 14-bit number from 0 to 0x3fff, indicating the wheel position. | |||
| If called for messages which aren't pitch wheel events, the number returned will be | |||
| nonsense. | |||
| @see isPitchWheel | |||
| */ | |||
| int getPitchWheelValue() const noexcept; | |||
| /** Creates a pitch-wheel move message. | |||
| @param channel the midi channel, in the range 1 to 16 | |||
| @param position the wheel position, in the range 0 to 16383 | |||
| @see isPitchWheel | |||
| */ | |||
| static MidiMessage pitchWheel (int channel, int position) noexcept; | |||
| //============================================================================== | |||
| /** Returns true if the message is an aftertouch event. | |||
| For aftertouch events, use the getNoteNumber() method to find out the key | |||
| that it applies to, and getAftertouchValue() to find out the amount. Use | |||
| getChannel() to find out the channel. | |||
| @see getAftertouchValue, getNoteNumber | |||
| */ | |||
| bool isAftertouch() const noexcept; | |||
| /** Returns the amount of aftertouch from an aftertouch messages. | |||
| The value returned is in the range 0 to 127, and will be nonsense for messages | |||
| other than aftertouch messages. | |||
| @see isAftertouch | |||
| */ | |||
| int getAfterTouchValue() const noexcept; | |||
| /** Creates an aftertouch message. | |||
| @param channel the midi channel, in the range 1 to 16 | |||
| @param noteNumber the key number, 0 to 127 | |||
| @param aftertouchAmount the amount of aftertouch, 0 to 127 | |||
| @see isAftertouch | |||
| */ | |||
| static MidiMessage aftertouchChange (int channel, | |||
| int noteNumber, | |||
| int aftertouchAmount) noexcept; | |||
| /** Returns true if the message is a channel-pressure change event. | |||
| This is like aftertouch, but common to the whole channel rather than a specific | |||
| note. Use getChannelPressureValue() to find out the pressure, and getChannel() | |||
| to find out the channel. | |||
| @see channelPressureChange | |||
| */ | |||
| bool isChannelPressure() const noexcept; | |||
| /** Returns the pressure from a channel pressure change message. | |||
| @returns the pressure, in the range 0 to 127 | |||
| @see isChannelPressure, channelPressureChange | |||
| */ | |||
| int getChannelPressureValue() const noexcept; | |||
| /** Creates a channel-pressure change event. | |||
| @param channel the midi channel: 1 to 16 | |||
| @param pressure the pressure, 0 to 127 | |||
| @see isChannelPressure | |||
| */ | |||
| static MidiMessage channelPressureChange (int channel, int pressure) noexcept; | |||
| //============================================================================== | |||
| /** Returns true if this is a midi controller message. | |||
| @see getControllerNumber, getControllerValue, controllerEvent | |||
| */ | |||
| bool isController() const noexcept; | |||
| /** Returns the controller number of a controller message. | |||
| The name of the controller can be looked up using the getControllerName() method. | |||
| Note that the value returned is invalid for messages that aren't controller changes. | |||
| @see isController, getControllerName, getControllerValue | |||
| */ | |||
| int getControllerNumber() const noexcept; | |||
| /** Returns the controller value from a controller message. | |||
| A value 0 to 127 is returned to indicate the new controller position. | |||
| Note that the value returned is invalid for messages that aren't controller changes. | |||
| @see isController, getControllerNumber | |||
| */ | |||
| int getControllerValue() const noexcept; | |||
| /** Returns true if this message is a controller message and if it has the specified | |||
| controller type. | |||
| */ | |||
| bool isControllerOfType (int controllerType) const noexcept; | |||
| /** Creates a controller message. | |||
| @param channel the midi channel, in the range 1 to 16 | |||
| @param controllerType the type of controller | |||
| @param value the controller value | |||
| @see isController | |||
| */ | |||
| static MidiMessage controllerEvent (int channel, | |||
| int controllerType, | |||
| int value) noexcept; | |||
| /** Checks whether this message is an all-notes-off message. | |||
| @see allNotesOff | |||
| */ | |||
| bool isAllNotesOff() const noexcept; | |||
| /** Checks whether this message is an all-sound-off message. | |||
| @see allSoundOff | |||
| */ | |||
| bool isAllSoundOff() const noexcept; | |||
| /** Creates an all-notes-off message. | |||
| @param channel the midi channel, in the range 1 to 16 | |||
| @see isAllNotesOff | |||
| */ | |||
| static MidiMessage allNotesOff (int channel) noexcept; | |||
| /** Creates an all-sound-off message. | |||
| @param channel the midi channel, in the range 1 to 16 | |||
| @see isAllSoundOff | |||
| */ | |||
| static MidiMessage allSoundOff (int channel) noexcept; | |||
| /** Creates an all-controllers-off message. | |||
| @param channel the midi channel, in the range 1 to 16 | |||
| */ | |||
| static MidiMessage allControllersOff (int channel) noexcept; | |||
| //============================================================================== | |||
| /** Returns true if this event is a meta-event. | |||
| Meta-events are things like tempo changes, track names, etc. | |||
| @see getMetaEventType, isTrackMetaEvent, isEndOfTrackMetaEvent, | |||
| isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent, | |||
| isKeySignatureMetaEvent, isMidiChannelMetaEvent | |||
| */ | |||
| bool isMetaEvent() const noexcept; | |||
| /** Returns a meta-event's type number. | |||
| If the message isn't a meta-event, this will return -1. | |||
| @see isMetaEvent, isTrackMetaEvent, isEndOfTrackMetaEvent, | |||
| isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent, | |||
| isKeySignatureMetaEvent, isMidiChannelMetaEvent | |||
| */ | |||
| int getMetaEventType() const noexcept; | |||
| /** Returns a pointer to the data in a meta-event. | |||
| @see isMetaEvent, getMetaEventLength | |||
| */ | |||
| const uint8* getMetaEventData() const noexcept; | |||
| /** Returns the length of the data for a meta-event. | |||
| @see isMetaEvent, getMetaEventData | |||
| */ | |||
| int getMetaEventLength() const noexcept; | |||
| //============================================================================== | |||
| /** Returns true if this is a 'track' meta-event. */ | |||
| bool isTrackMetaEvent() const noexcept; | |||
| /** Returns true if this is an 'end-of-track' meta-event. */ | |||
| bool isEndOfTrackMetaEvent() const noexcept; | |||
| /** Creates an end-of-track meta-event. | |||
| @see isEndOfTrackMetaEvent | |||
| */ | |||
| static MidiMessage endOfTrack() noexcept; | |||
| /** Returns true if this is an 'track name' meta-event. | |||
| You can use the getTextFromTextMetaEvent() method to get the track's name. | |||
| */ | |||
| bool isTrackNameEvent() const noexcept; | |||
| /** Returns true if this is a 'text' meta-event. | |||
| @see getTextFromTextMetaEvent | |||
| */ | |||
| bool isTextMetaEvent() const noexcept; | |||
| /** Returns the text from a text meta-event. | |||
| @see isTextMetaEvent | |||
| */ | |||
| String getTextFromTextMetaEvent() const; | |||
| /** Creates a text meta-event. */ | |||
| static MidiMessage textMetaEvent (int type, StringRef text); | |||
| //============================================================================== | |||
| /** Returns true if this is a 'tempo' meta-event. | |||
| @see getTempoMetaEventTickLength, getTempoSecondsPerQuarterNote | |||
| */ | |||
| bool isTempoMetaEvent() const noexcept; | |||
| /** Returns the tick length from a tempo meta-event. | |||
| @param timeFormat the 16-bit time format value from the midi file's header. | |||
| @returns the tick length (in seconds). | |||
| @see isTempoMetaEvent | |||
| */ | |||
| double getTempoMetaEventTickLength (short timeFormat) const noexcept; | |||
| /** Calculates the seconds-per-quarter-note from a tempo meta-event. | |||
| @see isTempoMetaEvent, getTempoMetaEventTickLength | |||
| */ | |||
| double getTempoSecondsPerQuarterNote() const noexcept; | |||
| /** Creates a tempo meta-event. | |||
| @see isTempoMetaEvent | |||
| */ | |||
| static MidiMessage tempoMetaEvent (int microsecondsPerQuarterNote) noexcept; | |||
| //============================================================================== | |||
| /** Returns true if this is a 'time-signature' meta-event. | |||
| @see getTimeSignatureInfo | |||
| */ | |||
| bool isTimeSignatureMetaEvent() const noexcept; | |||
| /** Returns the time-signature values from a time-signature meta-event. | |||
| @see isTimeSignatureMetaEvent | |||
| */ | |||
| void getTimeSignatureInfo (int& numerator, int& denominator) const noexcept; | |||
| /** Creates a time-signature meta-event. | |||
| @see isTimeSignatureMetaEvent | |||
| */ | |||
| static MidiMessage timeSignatureMetaEvent (int numerator, int denominator); | |||
| //============================================================================== | |||
| /** Returns true if this is a 'key-signature' meta-event. | |||
| @see getKeySignatureNumberOfSharpsOrFlats, isKeySignatureMajorKey | |||
| */ | |||
| bool isKeySignatureMetaEvent() const noexcept; | |||
| /** Returns the key from a key-signature meta-event. | |||
| This method must only be called if isKeySignatureMetaEvent() is true. | |||
| A positive number here indicates the number of sharps in the key signature, | |||
| and a negative number indicates a number of flats. So e.g. 3 = F# + C# + G#, | |||
| -2 = Bb + Eb | |||
| @see isKeySignatureMetaEvent, isKeySignatureMajorKey | |||
| */ | |||
| int getKeySignatureNumberOfSharpsOrFlats() const noexcept; | |||
| /** Returns true if this key-signature event is major, or false if it's minor. | |||
| This method must only be called if isKeySignatureMetaEvent() is true. | |||
| */ | |||
| bool isKeySignatureMajorKey() const noexcept; | |||
| /** Creates a key-signature meta-event. | |||
| @param numberOfSharpsOrFlats if positive, this indicates the number of sharps | |||
| in the key; if negative, the number of flats | |||
| @param isMinorKey if true, the key is minor; if false, it is major | |||
| @see isKeySignatureMetaEvent | |||
| */ | |||
| static MidiMessage keySignatureMetaEvent (int numberOfSharpsOrFlats, bool isMinorKey); | |||
| //============================================================================== | |||
| /** Returns true if this is a 'channel' meta-event. | |||
| A channel meta-event specifies the midi channel that should be used | |||
| for subsequent meta-events. | |||
| @see getMidiChannelMetaEventChannel | |||
| */ | |||
| bool isMidiChannelMetaEvent() const noexcept; | |||
| /** Returns the channel number from a channel meta-event. | |||
| @returns the channel, in the range 1 to 16. | |||
| @see isMidiChannelMetaEvent | |||
| */ | |||
| int getMidiChannelMetaEventChannel() const noexcept; | |||
| /** Creates a midi channel meta-event. | |||
| @param channel the midi channel, in the range 1 to 16 | |||
| @see isMidiChannelMetaEvent | |||
| */ | |||
| static MidiMessage midiChannelMetaEvent (int channel) noexcept; | |||
| //============================================================================== | |||
| /** Returns true if this is an active-sense message. */ | |||
| bool isActiveSense() const noexcept; | |||
| //============================================================================== | |||
| /** Returns true if this is a midi start event. | |||
| @see midiStart | |||
| */ | |||
| bool isMidiStart() const noexcept; | |||
| /** Creates a midi start event. */ | |||
| static MidiMessage midiStart() noexcept; | |||
| /** Returns true if this is a midi continue event. | |||
| @see midiContinue | |||
| */ | |||
| bool isMidiContinue() const noexcept; | |||
| /** Creates a midi continue event. */ | |||
| static MidiMessage midiContinue() noexcept; | |||
| /** Returns true if this is a midi stop event. | |||
| @see midiStop | |||
| */ | |||
| bool isMidiStop() const noexcept; | |||
| /** Creates a midi stop event. */ | |||
| static MidiMessage midiStop() noexcept; | |||
| /** Returns true if this is a midi clock event. | |||
| @see midiClock, songPositionPointer | |||
| */ | |||
| bool isMidiClock() const noexcept; | |||
| /** Creates a midi clock event. */ | |||
| static MidiMessage midiClock() noexcept; | |||
| /** Returns true if this is a song-position-pointer message. | |||
| @see getSongPositionPointerMidiBeat, songPositionPointer | |||
| */ | |||
| bool isSongPositionPointer() const noexcept; | |||
| /** Returns the midi beat-number of a song-position-pointer message. | |||
| @see isSongPositionPointer, songPositionPointer | |||
| */ | |||
| int getSongPositionPointerMidiBeat() const noexcept; | |||
| /** Creates a song-position-pointer message. | |||
| The position is a number of midi beats from the start of the song, where 1 midi | |||
| beat is 6 midi clocks, and there are 24 midi clocks in a quarter-note. So there | |||
| are 4 midi beats in a quarter-note. | |||
| @see isSongPositionPointer, getSongPositionPointerMidiBeat | |||
| */ | |||
| static MidiMessage songPositionPointer (int positionInMidiBeats) noexcept; | |||
| //============================================================================== | |||
| /** Returns true if this is a quarter-frame midi timecode message. | |||
| @see quarterFrame, getQuarterFrameSequenceNumber, getQuarterFrameValue | |||
| */ | |||
| bool isQuarterFrame() const noexcept; | |||
| /** Returns the sequence number of a quarter-frame midi timecode message. | |||
| This will be a value between 0 and 7. | |||
| @see isQuarterFrame, getQuarterFrameValue, quarterFrame | |||
| */ | |||
| int getQuarterFrameSequenceNumber() const noexcept; | |||
| /** Returns the value from a quarter-frame message. | |||
| This will be the lower nybble of the message's data-byte, a value between 0 and 15 | |||
| */ | |||
| int getQuarterFrameValue() const noexcept; | |||
| /** Creates a quarter-frame MTC message. | |||
| @param sequenceNumber a value 0 to 7 for the upper nybble of the message's data byte | |||
| @param value a value 0 to 15 for the lower nybble of the message's data byte | |||
| */ | |||
| static MidiMessage quarterFrame (int sequenceNumber, int value) noexcept; | |||
| /** SMPTE timecode types. | |||
| Used by the getFullFrameParameters() and fullFrame() methods. | |||
| */ | |||
| enum SmpteTimecodeType | |||
| { | |||
| fps24 = 0, | |||
| fps25 = 1, | |||
| fps30drop = 2, | |||
| fps30 = 3 | |||
| }; | |||
| /** Returns true if this is a full-frame midi timecode message. */ | |||
| bool isFullFrame() const noexcept; | |||
| /** Extracts the timecode information from a full-frame midi timecode message. | |||
| You should only call this on messages where you've used isFullFrame() to | |||
| check that they're the right kind. | |||
| */ | |||
| void getFullFrameParameters (int& hours, | |||
| int& minutes, | |||
| int& seconds, | |||
| int& frames, | |||
| SmpteTimecodeType& timecodeType) const noexcept; | |||
| /** Creates a full-frame MTC message. */ | |||
| static MidiMessage fullFrame (int hours, | |||
| int minutes, | |||
| int seconds, | |||
| int frames, | |||
| SmpteTimecodeType timecodeType); | |||
| //============================================================================== | |||
| /** Types of MMC command. | |||
| @see isMidiMachineControlMessage, getMidiMachineControlCommand, midiMachineControlCommand | |||
| */ | |||
| enum MidiMachineControlCommand | |||
| { | |||
| mmc_stop = 1, | |||
| mmc_play = 2, | |||
| mmc_deferredplay = 3, | |||
| mmc_fastforward = 4, | |||
| mmc_rewind = 5, | |||
| mmc_recordStart = 6, | |||
| mmc_recordStop = 7, | |||
| mmc_pause = 9 | |||
| }; | |||
| /** Checks whether this is an MMC message. | |||
| If it is, you can use the getMidiMachineControlCommand() to find out its type. | |||
| */ | |||
| bool isMidiMachineControlMessage() const noexcept; | |||
| /** For an MMC message, this returns its type. | |||
| Make sure it's actually an MMC message with isMidiMachineControlMessage() before | |||
| calling this method. | |||
| */ | |||
| MidiMachineControlCommand getMidiMachineControlCommand() const noexcept; | |||
| /** Creates an MMC message. */ | |||
| static MidiMessage midiMachineControlCommand (MidiMachineControlCommand command); | |||
| /** Checks whether this is an MMC "goto" message. | |||
| If it is, the parameters passed-in are set to the time that the message contains. | |||
| @see midiMachineControlGoto | |||
| */ | |||
| bool isMidiMachineControlGoto (int& hours, | |||
| int& minutes, | |||
| int& seconds, | |||
| int& frames) const noexcept; | |||
| /** Creates an MMC "goto" message. | |||
| This messages tells the device to go to a specific frame. | |||
| @see isMidiMachineControlGoto | |||
| */ | |||
| static MidiMessage midiMachineControlGoto (int hours, | |||
| int minutes, | |||
| int seconds, | |||
| int frames); | |||
| //============================================================================== | |||
| /** Creates a master-volume change message. | |||
| @param volume the volume, 0 to 1.0 | |||
| */ | |||
| static MidiMessage masterVolume (float volume); | |||
| //============================================================================== | |||
| /** Creates a system-exclusive message. | |||
| The data passed in is wrapped with header and tail bytes of 0xf0 and 0xf7. | |||
| */ | |||
| static MidiMessage createSysExMessage (const void* sysexData, | |||
| int dataSize); | |||
| //============================================================================== | |||
| /** Reads a midi variable-length integer. | |||
| @param data the data to read the number from | |||
| @param numBytesUsed on return, this will be set to the number of bytes that were read | |||
| */ | |||
| static int readVariableLengthVal (const uint8* data, | |||
| int& numBytesUsed) noexcept; | |||
| /** Based on the first byte of a short midi message, this uses a lookup table | |||
| to return the message length (either 1, 2, or 3 bytes). | |||
| The value passed in must be 0x80 or higher. | |||
| */ | |||
| static int getMessageLengthFromFirstByte (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 true if the given midi note number is a black key. */ | |||
| static bool isMidiNoteBlack (int noteNumber) noexcept; | |||
| /** Returns the standard name of a GM instrument, or nullptr if unknown for this index. | |||
| @param midiInstrumentNumber the program number 0 to 127 | |||
| @see getProgramChangeNumber | |||
| */ | |||
| static const char* getGMInstrumentName (int midiInstrumentNumber); | |||
| /** Returns the name of a bank of GM instruments, or nullptr if unknown for this bank number. | |||
| @param midiBankNumber the bank, 0 to 15 | |||
| */ | |||
| static const char* getGMInstrumentBankName (int midiBankNumber); | |||
| /** Returns the standard name of a channel 10 percussion sound, or nullptr if unknown for this note number. | |||
| @param midiNoteNumber the key number, 35 to 81 | |||
| */ | |||
| static const char* getRhythmInstrumentName (int midiNoteNumber); | |||
| /** Returns the name of a controller type number, or nullptr if unknown for this controller number. | |||
| @see getControllerNumber | |||
| */ | |||
| static const char* getControllerName (int controllerNumber); | |||
| private: | |||
| //============================================================================== | |||
| double timeStamp; | |||
| HeapBlock<uint8> allocatedData; | |||
| int size; | |||
| #ifndef DOXYGEN | |||
| union | |||
| { | |||
| uint8 asBytes[4]; | |||
| uint32 asInt32; | |||
| } preallocatedData; | |||
| #endif | |||
| inline uint8* getData() noexcept { return allocatedData != nullptr ? allocatedData.getData() : preallocatedData.asBytes; } | |||
| uint8* allocateSpace (int); | |||
| }; | |||
| #endif // JUCE_MIDIMESSAGE_H_INCLUDED | |||
| @@ -1,331 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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.addCopiesOf (other.list); | |||
| updateMatchedPairs(); | |||
| } | |||
| 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 noexcept | |||
| { | |||
| return list.size(); | |||
| } | |||
| MidiMessageSequence::MidiEventHolder* MidiMessageSequence::getEventPointer (const int index) const noexcept | |||
| { | |||
| return list [index]; | |||
| } | |||
| double MidiMessageSequence::getTimeOfMatchingKeyUp (const int index) const noexcept | |||
| { | |||
| 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 noexcept | |||
| { | |||
| if (const MidiEventHolder* const meh = list [index]) | |||
| return list.indexOf (meh->noteOffObject); | |||
| return -1; | |||
| } | |||
| int MidiMessageSequence::getIndexOf (MidiEventHolder* const event) const noexcept | |||
| { | |||
| return list.indexOf (event); | |||
| } | |||
| int MidiMessageSequence::getNextIndexAtTime (const double timeStamp) const noexcept | |||
| { | |||
| 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 noexcept | |||
| { | |||
| return getEventTime (0); | |||
| } | |||
| double MidiMessageSequence::getEndTime() const noexcept | |||
| { | |||
| return getEventTime (list.size() - 1); | |||
| } | |||
| double MidiMessageSequence::getEventTime (const int index) const noexcept | |||
| { | |||
| 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() noexcept | |||
| { | |||
| MidiMessageSequenceSorter sorter; | |||
| list.sort (sorter, true); | |||
| } | |||
| void MidiMessageSequence::updateMatchedPairs() noexcept | |||
| { | |||
| 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) noexcept | |||
| { | |||
| 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() | |||
| { | |||
| } | |||
| @@ -1,265 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 | |||
| //============================================================================== | |||
| /** | |||
| 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&); | |||
| /** Replaces this sequence with another one. */ | |||
| MidiMessageSequence& operator= (const MidiMessageSequence&); | |||
| /** 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 nullptr. | |||
| 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&); | |||
| JUCE_LEAK_DETECTOR (MidiEventHolder) | |||
| }; | |||
| //============================================================================== | |||
| /** Clears the sequence. */ | |||
| void clear(); | |||
| /** Returns the number of events in the sequence. */ | |||
| int getNumEvents() const noexcept; | |||
| /** Returns a pointer to one of the events. */ | |||
| MidiEventHolder* getEventPointer (int index) const noexcept; | |||
| /** 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 noexcept; | |||
| /** 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 noexcept; | |||
| /** Returns the index of an event. */ | |||
| int getIndexOf (MidiEventHolder* event) const noexcept; | |||
| /** 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 noexcept; | |||
| //============================================================================== | |||
| /** Returns the timestamp of the first event in the sequence. | |||
| @see getEndTime | |||
| */ | |||
| double getStartTime() const noexcept; | |||
| /** Returns the timestamp of the last event in the sequence. | |||
| @see getStartTime | |||
| */ | |||
| double getEndTime() const noexcept; | |||
| /** 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 noexcept; | |||
| //============================================================================== | |||
| /** 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 re-ordering messages 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() noexcept; | |||
| /** 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() noexcept; | |||
| //============================================================================== | |||
| /** 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) noexcept; | |||
| //============================================================================== | |||
| /** 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&) noexcept; | |||
| private: | |||
| //============================================================================== | |||
| friend class MidiFile; | |||
| OwnedArray<MidiEventHolder> list; | |||
| JUCE_LEAK_DETECTOR (MidiMessageSequence) | |||
| }; | |||
| #endif // JUCE_MIDIMESSAGESEQUENCE_H_INCLUDED | |||
| @@ -1,181 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 | |||
| //============================================================================== | |||
| /** | |||
| 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 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 | |||
| @@ -1,259 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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* s, | |||
| TimeSliceThread& thread, | |||
| const bool deleteSourceWhenDeleted, | |||
| const int bufferSizeSamples, | |||
| const int numChannels) | |||
| : source (s, deleteSourceWhenDeleted), | |||
| backgroundThread (thread), | |||
| numberOfSamplesToBuffer (jmax (1024, bufferSizeSamples)), | |||
| numberOfChannels (numChannels), | |||
| 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 newSampleRate) | |||
| { | |||
| const int bufferSizeNeeded = jmax (samplesPerBlockExpected * 2, numberOfSamplesToBuffer); | |||
| if (newSampleRate != sampleRate | |||
| || bufferSizeNeeded != buffer.getNumSamples() | |||
| || ! isPrepared) | |||
| { | |||
| backgroundThread.removeTimeSliceClient (this); | |||
| isPrepared = true; | |||
| sampleRate = newSampleRate; | |||
| source->prepareToPlay (samplesPerBlockExpected, newSampleRate); | |||
| buffer.setSize (numberOfChannels, bufferSizeNeeded); | |||
| buffer.clear(); | |||
| bufferValidStart = 0; | |||
| bufferValidEnd = 0; | |||
| backgroundThread.addTimeSliceClient (this); | |||
| while (bufferValidEnd - bufferValidStart < jmin (((int) newSampleRate) / 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; | |||
| } | |||
| @@ -1,111 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 | |||
| //============================================================================== | |||
| /** | |||
| 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&) 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() override; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BufferingAudioSource) | |||
| }; | |||
| #endif // JUCE_BUFFERINGAUDIOSOURCE_H_INCLUDED | |||
| @@ -1,184 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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) | |||
| { | |||
| 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()); | |||
| } | |||
| } | |||
| @@ -1,143 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 | |||
| //============================================================================== | |||
| /** | |||
| 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&); | |||
| //============================================================================== | |||
| void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; | |||
| void releaseResources() override; | |||
| void getNextAudioBlock (const AudioSourceChannelInfo&) override; | |||
| 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 | |||
| @@ -1,77 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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->getWritePointer (i, bufferToFill.startSample), | |||
| bufferToFill.numSamples); | |||
| } | |||
| @@ -1,70 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 | |||
| //============================================================================== | |||
| /** | |||
| 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) override; | |||
| void releaseResources() override; | |||
| void getNextAudioBlock (const AudioSourceChannelInfo&) override; | |||
| private: | |||
| //============================================================================== | |||
| OptionalScopedPointer<AudioSource> input; | |||
| OwnedArray<IIRFilter> iirFilters; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (IIRFilterAudioSource) | |||
| }; | |||
| #endif // JUCE_IIRFILTERAUDIOSOURCE_H_INCLUDED | |||
| @@ -1,155 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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() | |||
| : 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(); | |||
| } | |||
| } | |||
| @@ -1,101 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 | |||
| //============================================================================== | |||
| /** | |||
| 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) override; | |||
| /** Implementation of the AudioSource method. | |||
| This will call releaseResources() on all its input sources. | |||
| */ | |||
| void releaseResources() override; | |||
| /** Implementation of the AudioSource method. */ | |||
| void getNextAudioBlock (const AudioSourceChannelInfo&) override; | |||
| 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 | |||
| @@ -1,78 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 | |||
| //============================================================================== | |||
| /** | |||
| 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 | |||
| @@ -1,261 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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), | |||
| 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); | |||
| filterStates.calloc ((size_t) numChannels); | |||
| srcBuffers.calloc ((size_t) numChannels); | |||
| destBuffers.calloc ((size_t) numChannels); | |||
| createLowPass (ratio); | |||
| flushBuffers(); | |||
| } | |||
| void ResamplingAudioSource::flushBuffers() | |||
| { | |||
| buffer.clear(); | |||
| bufferPos = 0; | |||
| sampsInBuffer = 0; | |||
| subSampleOffset = 0.0; | |||
| 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.getWritePointer (i, endOfBufferPos), numToDo, filterStates[i]); | |||
| } | |||
| sampsInBuffer += numToDo; | |||
| endOfBufferPos += numToDo; | |||
| } | |||
| for (int channel = 0; channel < channelsToProcess; ++channel) | |||
| { | |||
| destBuffers[channel] = info.buffer->getWritePointer (channel, info.startSample); | |||
| srcBuffers[channel] = buffer.getReadPointer (channel); | |||
| } | |||
| 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->getWritePointer (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->getReadPointer (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() | |||
| { | |||
| if (filterStates != nullptr) | |||
| 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; | |||
| } | |||
| } | |||
| @@ -1,107 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 | |||
| //============================================================================== | |||
| /** | |||
| 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; } | |||
| /** Clears any buffers and filters that the resampler is using. */ | |||
| void flushBuffers(); | |||
| //============================================================================== | |||
| void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; | |||
| void releaseResources() override; | |||
| void getNextAudioBlock (const AudioSourceChannelInfo&) override; | |||
| 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; | |||
| HeapBlock<const float*> 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 | |||
| @@ -1,80 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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->getWritePointer (0, bufferToFill.startSample); | |||
| if (bufferToFill.buffer->getNumChannels() > 1) | |||
| { | |||
| reverb.processStereo (firstChannel, | |||
| bufferToFill.buffer->getWritePointer (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(); | |||
| } | |||
| } | |||
| @@ -1,76 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 | |||
| //============================================================================== | |||
| /** | |||
| 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) override; | |||
| void releaseResources() override; | |||
| void getNextAudioBlock (const AudioSourceChannelInfo&) override; | |||
| private: | |||
| //============================================================================== | |||
| CriticalSection lock; | |||
| OptionalScopedPointer<AudioSource> input; | |||
| Reverb reverb; | |||
| volatile bool bypass; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ReverbAudioSource) | |||
| }; | |||
| #endif // JUCE_REVERBAUDIOSOURCE_H_INCLUDED | |||
| @@ -1,75 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 rate) | |||
| { | |||
| currentPhase = 0.0; | |||
| phasePerSample = 0.0; | |||
| sampleRate = rate; | |||
| } | |||
| 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->setSample (j, info.startSample + i, sample); | |||
| } | |||
| } | |||
| @@ -1,73 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 | |||
| //============================================================================== | |||
| /** | |||
| 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) override; | |||
| /** Implementation of the AudioSource method. */ | |||
| void releaseResources() override; | |||
| /** Implementation of the AudioSource method. */ | |||
| void getNextAudioBlock (const AudioSourceChannelInfo&) override; | |||
| private: | |||
| //============================================================================== | |||
| double frequency, sampleRate; | |||
| double currentPhase, phasePerSample; | |||
| float amplitude; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToneGeneratorAudioSource) | |||
| }; | |||
| #endif // JUCE_TONEGENERATORAUDIOSOURCE_H_INCLUDED | |||
| @@ -1,503 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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; | |||
| } | |||
| bool SynthesiserVoice::isVoiceActive() const | |||
| { | |||
| return getCurrentlyPlayingNote() >= 0; | |||
| } | |||
| void SynthesiserVoice::clearCurrentNote() | |||
| { | |||
| currentlyPlayingNote = -1; | |||
| currentlyPlayingSound = nullptr; | |||
| } | |||
| void SynthesiserVoice::aftertouchChanged (int) {} | |||
| bool SynthesiserVoice::wasStartedBefore (const SynthesiserVoice& other) const noexcept | |||
| { | |||
| return noteOnTime < other.noteOnTime; | |||
| } | |||
| //============================================================================== | |||
| 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(); | |||
| } | |||
| SynthesiserVoice* Synthesiser::addVoice (SynthesiserVoice* const newVoice) | |||
| { | |||
| const ScopedLock sl (lock); | |||
| return 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(); | |||
| } | |||
| SynthesiserSound* Synthesiser::addSound (const SynthesiserSound::Ptr& newSound) | |||
| { | |||
| const ScopedLock sl (lock); | |||
| return sounds.add (newSound); | |||
| } | |||
| void Synthesiser::removeSound (const int index) | |||
| { | |||
| const ScopedLock sl (lock); | |||
| sounds.remove (index); | |||
| } | |||
| void Synthesiser::setNoteStealingEnabled (const bool shouldSteal) | |||
| { | |||
| shouldStealNotes = shouldSteal; | |||
| } | |||
| //============================================================================== | |||
| 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) | |||
| renderVoices (outputBuffer, startSample, numThisTime); | |||
| if (useEvent) | |||
| handleMidiEvent (m); | |||
| startSample += numThisTime; | |||
| numSamples -= numThisTime; | |||
| } | |||
| } | |||
| void Synthesiser::renderVoices (AudioSampleBuffer& buffer, int startSample, int numSamples) | |||
| { | |||
| for (int i = voices.size(); --i >= 0;) | |||
| voices.getUnchecked (i)->renderNextBlock (buffer, startSample, numSamples); | |||
| } | |||
| 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(), m.getFloatVelocity(), 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.isAftertouch()) | |||
| { | |||
| handleAftertouch (m.getChannel(), m.getNoteNumber(), m.getAfterTouchValue()); | |||
| } | |||
| 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, 1.0f, true); | |||
| } | |||
| startVoice (findFreeVoice (sound, midiChannel, midiNoteNumber, 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 (0.0f, 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, float velocity, const bool allowTailOff) | |||
| { | |||
| jassert (voice != nullptr); | |||
| voice->stopNote (velocity, 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 float velocity, | |||
| 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, velocity, 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 (1.0f, 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::handleAftertouch (int midiChannel, int midiNoteNumber, int aftertouchValue) | |||
| { | |||
| const ScopedLock sl (lock); | |||
| for (int i = voices.size(); --i >= 0;) | |||
| { | |||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
| if (voice->getCurrentlyPlayingNote() == midiNoteNumber | |||
| && (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))) | |||
| voice->aftertouchChanged (aftertouchValue); | |||
| } | |||
| } | |||
| 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, 1.0f, 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, 1.0f, true); | |||
| } | |||
| } | |||
| } | |||
| void Synthesiser::handleSoftPedal (int midiChannel, bool /*isDown*/) | |||
| { | |||
| (void) midiChannel; | |||
| jassert (midiChannel > 0 && midiChannel <= 16); | |||
| } | |||
| //============================================================================== | |||
| SynthesiserVoice* Synthesiser::findFreeVoice (SynthesiserSound* soundToPlay, | |||
| int midiChannel, int midiNoteNumber, | |||
| const bool stealIfNoneAvailable) const | |||
| { | |||
| const ScopedLock sl (lock); | |||
| for (int i = 0; i < voices.size(); ++i) | |||
| { | |||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
| if ((! voice->isVoiceActive()) && voice->canPlaySound (soundToPlay)) | |||
| return voice; | |||
| } | |||
| if (stealIfNoneAvailable) | |||
| return findVoiceToSteal (soundToPlay, midiChannel, midiNoteNumber); | |||
| return nullptr; | |||
| } | |||
| struct VoiceAgeSorter | |||
| { | |||
| static int compareElements (SynthesiserVoice* v1, SynthesiserVoice* v2) noexcept | |||
| { | |||
| return v1->wasStartedBefore (*v2) ? 1 : (v2->wasStartedBefore (*v1) ? -1 : 0); | |||
| } | |||
| }; | |||
| SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, | |||
| int /*midiChannel*/, int midiNoteNumber) const | |||
| { | |||
| SynthesiserVoice* bottom = nullptr; | |||
| SynthesiserVoice* top = nullptr; | |||
| // this is a list of voices we can steal, sorted by how long they've been running | |||
| Array<SynthesiserVoice*> usableVoices; | |||
| usableVoices.ensureStorageAllocated (voices.size()); | |||
| for (int i = 0; i < voices.size(); ++i) | |||
| { | |||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
| if (voice->canPlaySound (soundToPlay)) | |||
| { | |||
| VoiceAgeSorter sorter; | |||
| usableVoices.addSorted (sorter, voice); | |||
| const int note = voice->getCurrentlyPlayingNote(); | |||
| if (bottom == nullptr || note < bottom->getCurrentlyPlayingNote()) | |||
| bottom = voice; | |||
| if (top == nullptr || note > top->getCurrentlyPlayingNote()) | |||
| top = voice; | |||
| } | |||
| } | |||
| jassert (bottom != nullptr && top != nullptr); | |||
| // The oldest note that's playing with the target pitch playing is ideal.. | |||
| for (int i = 0; i < usableVoices.size(); ++i) | |||
| { | |||
| SynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||
| if (voice->getCurrentlyPlayingNote() == midiNoteNumber) | |||
| return voice; | |||
| } | |||
| // ..otherwise, look for the oldest note that isn't the top or bottom note.. | |||
| for (int i = 0; i < usableVoices.size(); ++i) | |||
| { | |||
| SynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||
| if (voice != bottom && voice != top) | |||
| return voice; | |||
| } | |||
| // ..otherwise, there's only one or two voices to choose from - we'll return the top one.. | |||
| return top; | |||
| } | |||
| @@ -1,557 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 | |||
| //============================================================================== | |||
| /** | |||
| 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 (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 (int midiChannel) = 0; | |||
| /** The class is reference-counted, so this is a handy pointer class for it. */ | |||
| 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 noexcept { return currentlyPlayingNote; } | |||
| /** Returns the sound that this voice is currently playing. | |||
| Returns nullptr if it's not playing. | |||
| */ | |||
| SynthesiserSound::Ptr getCurrentlyPlayingSound() const noexcept { 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*) = 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 (int midiNoteNumber, | |||
| float velocity, | |||
| SynthesiserSound* sound, | |||
| int currentPitchWheelPosition) = 0; | |||
| /** Called to stop a note. | |||
| This will be called during the rendering callback, so must be fast and thread-safe. | |||
| The velocity indicates how quickly the note was released - 0 is slowly, 1 is quickly. | |||
| 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 (float velocity, bool allowTailOff) = 0; | |||
| /** Returns true if this voice is currently busy playing a sound. | |||
| By default this just checks the getCurrentlyPlayingNote() value, but can | |||
| be overridden for more advanced checking. | |||
| */ | |||
| virtual bool isVoiceActive() const; | |||
| /** 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 (int newPitchWheelValue) = 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 (int controllerNumber, int newControllerValue) = 0; | |||
| /** Called to let the voice know that the aftertouch has changed. | |||
| This will be called during the rendering callback, so must be fast and thread-safe. | |||
| */ | |||
| virtual void aftertouchChanged (int newAftertouchValue); | |||
| //============================================================================== | |||
| /** 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; | |||
| /** 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. | |||
| */ | |||
| virtual void setCurrentPlaybackSampleRate (double newRate); | |||
| /** Returns the current target sample rate at which rendering is being done. | |||
| Subclasses may need to know this so that they can pitch things correctly. | |||
| */ | |||
| double getSampleRate() const noexcept { return currentSampleRate; } | |||
| /** 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; | |||
| /** Returns true if the key that triggered this voice is still held down. | |||
| Note that the voice may still be playing after the key was released (e.g because the | |||
| sostenuto pedal is down). | |||
| */ | |||
| bool isKeyDown() const noexcept { return keyIsDown; } | |||
| /** Returns true if the sostenuto pedal is currently active for this voice. */ | |||
| bool isSostenutoPedalDown() const noexcept { return sostenutoPedalDown; } | |||
| /** Returns true if this voice started playing its current note before the other voice did. */ | |||
| bool wasStartedBefore (const SynthesiserVoice& other) const noexcept; | |||
| protected: | |||
| /** 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, sostenutoPedalDown; | |||
| #if JUCE_CATCH_DEPRECATED_CODE_MISUSE | |||
| // Note the new parameters for this method. | |||
| virtual int stopNote (bool) { return 0; } | |||
| #endif | |||
| 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 noexcept { 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. | |||
| */ | |||
| SynthesiserVoice* 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 noexcept { return sounds.size(); } | |||
| /** Returns one of the sounds. */ | |||
| SynthesiserSound* getSound (int index) const noexcept { return sounds [index]; } | |||
| /** Adds a new sound to the synthesiser. | |||
| The object passed in is reference counted, so will be deleted when the | |||
| synthesiser and all voices are no longer using it. | |||
| */ | |||
| SynthesiserSound* 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 noexcept { 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, | |||
| float velocity, | |||
| 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 to any active voices. | |||
| 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 to any active voices. | |||
| 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); | |||
| /** Sends an aftertouch message. | |||
| This will send an aftertouch message to any voices that are playing sounds on | |||
| the given midi channel and note number. | |||
| 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 midiNoteNumber the midi note number, 0 to 127 | |||
| @param aftertouchValue the aftertouch value, between 0 and 127, | |||
| as returned by MidiMessage::getAftertouchValue() | |||
| */ | |||
| virtual void handleAftertouch (int midiChannel, int midiNoteNumber, int aftertouchValue); | |||
| /** Handles a sustain pedal event. */ | |||
| virtual void handleSustainPedal (int midiChannel, bool isDown); | |||
| /** Handles a sostenuto pedal event. */ | |||
| virtual void handleSostenutoPedal (int midiChannel, bool isDown); | |||
| /** Can be overridden to handle soft pedal events. */ | |||
| 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. | |||
| */ | |||
| virtual 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); | |||
| /** Returns the current target sample rate at which rendering is being done. | |||
| Subclasses may need to know this so that they can pitch things correctly. | |||
| */ | |||
| double getSampleRate() const noexcept { return sampleRate; } | |||
| 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]; | |||
| /** Renders the voices for the given range. | |||
| By default this just calls renderNextBlock() on each voice, but you may need | |||
| to override it to handle custom cases. | |||
| */ | |||
| virtual void renderVoices (AudioSampleBuffer& outputAudio, | |||
| int startSample, int numSamples); | |||
| /** 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. | |||
| To implement a custom note-stealing algorithm, you can either override this | |||
| method, or (preferably) override findVoiceToSteal(). | |||
| */ | |||
| virtual SynthesiserVoice* findFreeVoice (SynthesiserSound* soundToPlay, | |||
| int midiChannel, | |||
| int midiNoteNumber, | |||
| bool stealIfNoneAvailable) const; | |||
| /** Chooses a voice that is most suitable for being re-used. | |||
| The default method will attempt to find the oldest voice that isn't the | |||
| bottom or top note being played. If that's not suitable for your synth, | |||
| you can override this method and do something more cunning instead. | |||
| */ | |||
| virtual SynthesiserVoice* findVoiceToSteal (SynthesiserSound* soundToPlay, | |||
| int midiChannel, | |||
| int midiNoteNumber) 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*, float velocity, bool allowTailOff); | |||
| #if JUCE_CATCH_DEPRECATED_CODE_MISUSE | |||
| // Note the new parameters for these methods. | |||
| virtual int findFreeVoice (const bool) const { return 0; } | |||
| virtual int noteOff (int, int, int) { return 0; } | |||
| virtual int findFreeVoice (SynthesiserSound*, const bool) { return 0; } | |||
| virtual int findVoiceToSteal (SynthesiserSound*) const { return 0; } | |||
| #endif | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Synthesiser) | |||
| }; | |||
| #endif // JUCE_SYNTHESISER_H_INCLUDED | |||
| @@ -1,169 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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_AUDIOCDBURNER_H_INCLUDED | |||
| #define JUCE_AUDIOCDBURNER_H_INCLUDED | |||
| #if JUCE_USE_CDBURNER || DOXYGEN | |||
| //============================================================================== | |||
| /** | |||
| */ | |||
| class AudioCDBurner : public ChangeBroadcaster | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Returns a list of available optical drives. | |||
| Use openDevice() to open one of the items from this list. | |||
| */ | |||
| static StringArray findAvailableDevices(); | |||
| /** Tries to open one of the optical drives. | |||
| The deviceIndex is an index into the array returned by findAvailableDevices(). | |||
| */ | |||
| static AudioCDBurner* openDevice (const int deviceIndex); | |||
| /** Destructor. */ | |||
| ~AudioCDBurner(); | |||
| //============================================================================== | |||
| enum DiskState | |||
| { | |||
| unknown, /**< An error condition, if the device isn't responding. */ | |||
| trayOpen, /**< The drive is currently open. Note that a slot-loading drive | |||
| may seem to be permanently open. */ | |||
| noDisc, /**< The drive has no disk in it. */ | |||
| writableDiskPresent, /**< The drive contains a writeable disk. */ | |||
| readOnlyDiskPresent /**< The drive contains a read-only disk. */ | |||
| }; | |||
| /** Returns the current status of the device. | |||
| To get informed when the drive's status changes, attach a ChangeListener to | |||
| the AudioCDBurner. | |||
| */ | |||
| DiskState getDiskState() const; | |||
| /** Returns true if there's a writable disk in the drive. */ | |||
| bool isDiskPresent() const; | |||
| /** Sends an eject signal to the drive. | |||
| The eject will happen asynchronously, so you can use getDiskState() and | |||
| waitUntilStateChange() to monitor its progress. | |||
| */ | |||
| bool openTray(); | |||
| /** Blocks the current thread until the drive's state changes, or until the timeout expires. | |||
| @returns the device's new state | |||
| */ | |||
| DiskState waitUntilStateChange (int timeOutMilliseconds); | |||
| //============================================================================== | |||
| /** Returns the set of possible write speeds that the device can handle. | |||
| These are as a multiple of 'normal' speed, so e.g. '24x' returns 24, etc. | |||
| Note that if there's no media present in the drive, this value may be unavailable! | |||
| @see setWriteSpeed, getWriteSpeed | |||
| */ | |||
| Array<int> getAvailableWriteSpeeds() const; | |||
| //============================================================================== | |||
| /** Tries to enable or disable buffer underrun safety on devices that support it. | |||
| @returns true if it's now enabled. If the device doesn't support it, this | |||
| will always return false. | |||
| */ | |||
| bool setBufferUnderrunProtection (bool shouldBeEnabled); | |||
| //============================================================================== | |||
| /** Returns the number of free blocks on the disk. | |||
| There are 75 blocks per second, at 44100Hz. | |||
| */ | |||
| int getNumAvailableAudioBlocks() const; | |||
| /** Adds a track to be written. | |||
| The source passed-in here will be kept by this object, and it will | |||
| be used and deleted at some point in the future, either during the | |||
| burn() method or when this AudioCDBurner object is deleted. Your caller | |||
| method shouldn't keep a reference to it or use it again after passing | |||
| it in here. | |||
| */ | |||
| bool addAudioTrack (AudioSource* source, int numSamples); | |||
| //============================================================================== | |||
| /** Receives progress callbacks during a cd-burn operation. | |||
| @see AudioCDBurner::burn() | |||
| */ | |||
| class BurnProgressListener | |||
| { | |||
| public: | |||
| BurnProgressListener() noexcept {} | |||
| virtual ~BurnProgressListener() {} | |||
| /** Called at intervals to report on the progress of the AudioCDBurner. | |||
| To cancel the burn, return true from this method. | |||
| */ | |||
| virtual bool audioCDBurnProgress (float proportionComplete) = 0; | |||
| }; | |||
| /** Runs the burn process. | |||
| This method will block until the operation is complete. | |||
| @param listener the object to receive callbacks about progress | |||
| @param ejectDiscAfterwards whether to eject the disk after the burn completes | |||
| @param performFakeBurnForTesting if true, no data will actually be written to the disk | |||
| @param writeSpeed one of the write speeds from getAvailableWriteSpeeds(), or | |||
| 0 or less to mean the fastest speed. | |||
| */ | |||
| String burn (BurnProgressListener* listener, | |||
| bool ejectDiscAfterwards, | |||
| bool performFakeBurnForTesting, | |||
| int writeSpeed); | |||
| /** If a burn operation is currently in progress, this tells it to stop | |||
| as soon as possible. | |||
| It's also possible to stop the burn process by returning true from | |||
| BurnProgressListener::audioCDBurnProgress() | |||
| */ | |||
| void abortBurn(); | |||
| private: | |||
| //============================================================================== | |||
| AudioCDBurner (const int deviceIndex); | |||
| class Pimpl; | |||
| friend struct ContainerDeletePolicy<Pimpl>; | |||
| ScopedPointer<Pimpl> pimpl; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioCDBurner) | |||
| }; | |||
| #endif | |||
| #endif // JUCE_AUDIOCDBURNER_H_INCLUDED | |||
| @@ -1,57 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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_CDREADER | |||
| int AudioCDReader::getNumTracks() const | |||
| { | |||
| return trackStartSamples.size() - 1; | |||
| } | |||
| int AudioCDReader::getPositionOfTrackStart (int trackNum) const | |||
| { | |||
| return trackStartSamples [trackNum]; | |||
| } | |||
| const Array<int>& AudioCDReader::getTrackOffsets() const | |||
| { | |||
| return trackStartSamples; | |||
| } | |||
| int AudioCDReader::getCDDBId() | |||
| { | |||
| int checksum = 0; | |||
| const int numTracks = getNumTracks(); | |||
| for (int i = 0; i < numTracks; ++i) | |||
| for (int offset = (trackStartSamples.getUnchecked(i) + 88200) / 44100; offset > 0; offset /= 10) | |||
| checksum += offset % 10; | |||
| const int length = (trackStartSamples.getLast() - trackStartSamples.getFirst()) / 44100; | |||
| // CCLLLLTT: checksum, length, tracks | |||
| return ((checksum & 0xff) << 24) | (length << 8) | numTracks; | |||
| } | |||
| #endif | |||
| @@ -1,174 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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_AUDIOCDREADER_H_INCLUDED | |||
| #define JUCE_AUDIOCDREADER_H_INCLUDED | |||
| #if JUCE_USE_CDREADER || DOXYGEN | |||
| //============================================================================== | |||
| /** | |||
| A type of AudioFormatReader that reads from an audio CD. | |||
| One of these can be used to read a CD as if it's one big audio stream. Use the | |||
| getPositionOfTrackStart() method to find where the individual tracks are | |||
| within the stream. | |||
| @see AudioFormatReader | |||
| */ | |||
| class JUCE_API AudioCDReader : public AudioFormatReader | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Returns a list of names of Audio CDs currently available for reading. | |||
| If there's a CD drive but no CD in it, this might return an empty list, or | |||
| possibly a device that can be opened but which has no tracks, depending | |||
| on the platform. | |||
| @see createReaderForCD | |||
| */ | |||
| static StringArray getAvailableCDNames(); | |||
| /** Tries to create an AudioFormatReader that can read from an Audio CD. | |||
| @param index the index of one of the available CDs - use getAvailableCDNames() | |||
| to find out how many there are. | |||
| @returns a new AudioCDReader object, or nullptr if it couldn't be created. The | |||
| caller will be responsible for deleting the object returned. | |||
| */ | |||
| static AudioCDReader* createReaderForCD (const int index); | |||
| //============================================================================== | |||
| /** Destructor. */ | |||
| ~AudioCDReader(); | |||
| /** Implementation of the AudioFormatReader method. */ | |||
| bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | |||
| int64 startSampleInFile, int numSamples) override; | |||
| /** Checks whether the CD has been removed from the drive. */ | |||
| bool isCDStillPresent() const; | |||
| /** Returns the total number of tracks (audio + data). */ | |||
| int getNumTracks() const; | |||
| /** Finds the sample offset of the start of a track. | |||
| @param trackNum the track number, where trackNum = 0 is the first track | |||
| and trackNum = getNumTracks() means the end of the CD. | |||
| */ | |||
| int getPositionOfTrackStart (int trackNum) const; | |||
| /** Returns true if a given track is an audio track. | |||
| @param trackNum the track number, where 0 is the first track. | |||
| */ | |||
| bool isTrackAudio (int trackNum) const; | |||
| /** Returns an array of sample offsets for the start of each track, followed by | |||
| the sample position of the end of the CD. | |||
| */ | |||
| const Array<int>& getTrackOffsets() const; | |||
| /** Refreshes the object's table of contents. | |||
| If the disc has been ejected and a different one put in since this | |||
| object was created, this will cause it to update its idea of how many tracks | |||
| there are, etc. | |||
| */ | |||
| void refreshTrackLengths(); | |||
| /** Enables scanning for indexes within tracks. | |||
| @see getLastIndex | |||
| */ | |||
| void enableIndexScanning (bool enabled); | |||
| /** Returns the index number found during the last read() call. | |||
| Index scanning is turned off by default - turn it on with enableIndexScanning(). | |||
| Then when the read() method is called, if it comes across an index within that | |||
| block, the index number is stored and returned by this method. | |||
| Some devices might not support indexes, of course. | |||
| (If you don't know what CD indexes are, it's unlikely you'll ever need them). | |||
| @see enableIndexScanning | |||
| */ | |||
| int getLastIndex() const; | |||
| /** Scans a track to find the position of any indexes within it. | |||
| @param trackNumber the track to look in, where 0 is the first track on the disc | |||
| @returns an array of sample positions of any index points found (not including | |||
| the index that marks the start of the track) | |||
| */ | |||
| Array<int> findIndexesInTrack (const int trackNumber); | |||
| /** Returns the CDDB id number for the CD. | |||
| It's not a great way of identifying a disc, but it's traditional. | |||
| */ | |||
| int getCDDBId(); | |||
| /** Tries to eject the disk. | |||
| Ejecting the disk might not actually be possible, e.g. if some other process is using it. | |||
| */ | |||
| void ejectDisk(); | |||
| //============================================================================== | |||
| enum | |||
| { | |||
| framesPerSecond = 75, | |||
| samplesPerFrame = 44100 / framesPerSecond | |||
| }; | |||
| private: | |||
| //============================================================================== | |||
| Array<int> trackStartSamples; | |||
| #if JUCE_MAC | |||
| File volumeDir; | |||
| Array<File> tracks; | |||
| int currentReaderTrack; | |||
| ScopedPointer <AudioFormatReader> reader; | |||
| AudioCDReader (const File& volume); | |||
| #elif JUCE_WINDOWS | |||
| bool audioTracks [100]; | |||
| void* handle; | |||
| MemoryBlock buffer; | |||
| bool indexingEnabled; | |||
| int lastIndex, firstFrameInBuffer, samplesInBuffer; | |||
| AudioCDReader (void* handle); | |||
| int getIndexAt (int samplePos); | |||
| #elif JUCE_LINUX | |||
| AudioCDReader(); | |||
| #endif | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioCDReader) | |||
| }; | |||
| #endif | |||
| #endif // JUCE_AUDIOCDREADER_H_INCLUDED | |||
| @@ -1,987 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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. | |||
| ============================================================================== | |||
| */ | |||
| AudioDeviceManager::AudioDeviceSetup::AudioDeviceSetup() | |||
| : sampleRate (0), | |||
| bufferSize (0), | |||
| useDefaultInputChannels (true), | |||
| useDefaultOutputChannels (true) | |||
| { | |||
| } | |||
| bool AudioDeviceManager::AudioDeviceSetup::operator== (const AudioDeviceManager::AudioDeviceSetup& other) const | |||
| { | |||
| return outputDeviceName == other.outputDeviceName | |||
| && inputDeviceName == other.inputDeviceName | |||
| && sampleRate == other.sampleRate | |||
| && bufferSize == other.bufferSize | |||
| && inputChannels == other.inputChannels | |||
| && useDefaultInputChannels == other.useDefaultInputChannels | |||
| && outputChannels == other.outputChannels | |||
| && useDefaultOutputChannels == other.useDefaultOutputChannels; | |||
| } | |||
| //============================================================================== | |||
| class AudioDeviceManager::CallbackHandler : public AudioIODeviceCallback, | |||
| public MidiInputCallback, | |||
| public AudioIODeviceType::Listener | |||
| { | |||
| public: | |||
| CallbackHandler (AudioDeviceManager& adm) noexcept : owner (adm) {} | |||
| private: | |||
| void audioDeviceIOCallback (const float** ins, int numIns, float** outs, int numOuts, int numSamples) override | |||
| { | |||
| owner.audioDeviceIOCallbackInt (ins, numIns, outs, numOuts, numSamples); | |||
| } | |||
| void audioDeviceAboutToStart (AudioIODevice* device) override | |||
| { | |||
| owner.audioDeviceAboutToStartInt (device); | |||
| } | |||
| void audioDeviceStopped() override | |||
| { | |||
| owner.audioDeviceStoppedInt(); | |||
| } | |||
| void audioDeviceError (const String& message) override | |||
| { | |||
| owner.audioDeviceErrorInt (message); | |||
| } | |||
| void handleIncomingMidiMessage (MidiInput* source, const MidiMessage& message) override | |||
| { | |||
| owner.handleIncomingMidiMessageInt (source, message); | |||
| } | |||
| void audioDeviceListChanged() override | |||
| { | |||
| owner.audioDeviceListChanged(); | |||
| } | |||
| AudioDeviceManager& owner; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CallbackHandler) | |||
| }; | |||
| //============================================================================== | |||
| AudioDeviceManager::AudioDeviceManager() | |||
| : numInputChansNeeded (0), | |||
| numOutputChansNeeded (2), | |||
| listNeedsScanning (true), | |||
| useInputNames (false), | |||
| inputLevel (0), | |||
| testSoundPosition (0), | |||
| cpuUsageMs (0), | |||
| timeToCpuScale (0) | |||
| { | |||
| callbackHandler = new CallbackHandler (*this); | |||
| } | |||
| AudioDeviceManager::~AudioDeviceManager() | |||
| { | |||
| currentAudioDevice = nullptr; | |||
| defaultMidiOutput = nullptr; | |||
| } | |||
| //============================================================================== | |||
| void AudioDeviceManager::createDeviceTypesIfNeeded() | |||
| { | |||
| if (availableDeviceTypes.size() == 0) | |||
| { | |||
| OwnedArray<AudioIODeviceType> types; | |||
| createAudioDeviceTypes (types); | |||
| for (int i = 0; i < types.size(); ++i) | |||
| addAudioDeviceType (types.getUnchecked(i)); | |||
| types.clear (false); | |||
| if (AudioIODeviceType* first = availableDeviceTypes.getFirst()) | |||
| currentDeviceType = first->getTypeName(); | |||
| } | |||
| } | |||
| const OwnedArray<AudioIODeviceType>& AudioDeviceManager::getAvailableDeviceTypes() | |||
| { | |||
| scanDevicesIfNeeded(); | |||
| return availableDeviceTypes; | |||
| } | |||
| void AudioDeviceManager::audioDeviceListChanged() | |||
| { | |||
| if (currentAudioDevice != nullptr) | |||
| { | |||
| currentSetup.sampleRate = currentAudioDevice->getCurrentSampleRate(); | |||
| currentSetup.bufferSize = currentAudioDevice->getCurrentBufferSizeSamples(); | |||
| currentSetup.inputChannels = currentAudioDevice->getActiveInputChannels(); | |||
| currentSetup.outputChannels = currentAudioDevice->getActiveOutputChannels(); | |||
| } | |||
| sendChangeMessage(); | |||
| } | |||
| //============================================================================== | |||
| static void addIfNotNull (OwnedArray<AudioIODeviceType>& list, AudioIODeviceType* const device) | |||
| { | |||
| if (device != nullptr) | |||
| list.add (device); | |||
| } | |||
| void AudioDeviceManager::createAudioDeviceTypes (OwnedArray<AudioIODeviceType>& list) | |||
| { | |||
| addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_WASAPI()); | |||
| addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_DirectSound()); | |||
| addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_ASIO()); | |||
| addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_CoreAudio()); | |||
| addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_iOSAudio()); | |||
| addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_ALSA()); | |||
| addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_JACK()); | |||
| addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_OpenSLES()); | |||
| addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_Android()); | |||
| } | |||
| void AudioDeviceManager::addAudioDeviceType (AudioIODeviceType* newDeviceType) | |||
| { | |||
| if (newDeviceType != nullptr) | |||
| { | |||
| jassert (lastDeviceTypeConfigs.size() == availableDeviceTypes.size()); | |||
| availableDeviceTypes.add (newDeviceType); | |||
| lastDeviceTypeConfigs.add (new AudioDeviceSetup()); | |||
| newDeviceType->addListener (callbackHandler); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| String AudioDeviceManager::initialise (const int numInputChannelsNeeded, | |||
| const int numOutputChannelsNeeded, | |||
| const XmlElement* const xml, | |||
| const bool selectDefaultDeviceOnFailure, | |||
| const String& preferredDefaultDeviceName, | |||
| const AudioDeviceSetup* preferredSetupOptions) | |||
| { | |||
| scanDevicesIfNeeded(); | |||
| numInputChansNeeded = numInputChannelsNeeded; | |||
| numOutputChansNeeded = numOutputChannelsNeeded; | |||
| if (xml != nullptr && xml->hasTagName ("DEVICESETUP")) | |||
| return initialiseFromXML (*xml, selectDefaultDeviceOnFailure, | |||
| preferredDefaultDeviceName, preferredSetupOptions); | |||
| return initialiseDefault (preferredDefaultDeviceName, preferredSetupOptions); | |||
| } | |||
| String AudioDeviceManager::initialiseDefault (const String& preferredDefaultDeviceName, | |||
| const AudioDeviceSetup* preferredSetupOptions) | |||
| { | |||
| AudioDeviceSetup setup; | |||
| if (preferredSetupOptions != nullptr) | |||
| { | |||
| setup = *preferredSetupOptions; | |||
| } | |||
| else if (preferredDefaultDeviceName.isNotEmpty()) | |||
| { | |||
| for (int j = availableDeviceTypes.size(); --j >= 0;) | |||
| { | |||
| AudioIODeviceType* const type = availableDeviceTypes.getUnchecked(j); | |||
| const StringArray outs (type->getDeviceNames (false)); | |||
| for (int i = 0; i < outs.size(); ++i) | |||
| { | |||
| if (outs[i].matchesWildcard (preferredDefaultDeviceName, true)) | |||
| { | |||
| setup.outputDeviceName = outs[i]; | |||
| break; | |||
| } | |||
| } | |||
| const StringArray ins (type->getDeviceNames (true)); | |||
| for (int i = 0; i < ins.size(); ++i) | |||
| { | |||
| if (ins[i].matchesWildcard (preferredDefaultDeviceName, true)) | |||
| { | |||
| setup.inputDeviceName = ins[i]; | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| insertDefaultDeviceNames (setup); | |||
| return setAudioDeviceSetup (setup, false); | |||
| } | |||
| String AudioDeviceManager::initialiseFromXML (const XmlElement& xml, | |||
| const bool selectDefaultDeviceOnFailure, | |||
| const String& preferredDefaultDeviceName, | |||
| const AudioDeviceSetup* preferredSetupOptions) | |||
| { | |||
| lastExplicitSettings = new XmlElement (xml); | |||
| String error; | |||
| AudioDeviceSetup setup; | |||
| if (preferredSetupOptions != nullptr) | |||
| setup = *preferredSetupOptions; | |||
| if (xml.getStringAttribute ("audioDeviceName").isNotEmpty()) | |||
| { | |||
| setup.inputDeviceName = setup.outputDeviceName | |||
| = xml.getStringAttribute ("audioDeviceName"); | |||
| } | |||
| else | |||
| { | |||
| setup.inputDeviceName = xml.getStringAttribute ("audioInputDeviceName"); | |||
| setup.outputDeviceName = xml.getStringAttribute ("audioOutputDeviceName"); | |||
| } | |||
| currentDeviceType = xml.getStringAttribute ("deviceType"); | |||
| if (findType (currentDeviceType) == nullptr) | |||
| { | |||
| if (AudioIODeviceType* const type = findType (setup.inputDeviceName, setup.outputDeviceName)) | |||
| currentDeviceType = type->getTypeName(); | |||
| else if (availableDeviceTypes.size() > 0) | |||
| currentDeviceType = availableDeviceTypes.getUnchecked(0)->getTypeName(); | |||
| } | |||
| setup.bufferSize = xml.getIntAttribute ("audioDeviceBufferSize"); | |||
| setup.sampleRate = xml.getDoubleAttribute ("audioDeviceRate"); | |||
| setup.inputChannels .parseString (xml.getStringAttribute ("audioDeviceInChans", "11"), 2); | |||
| setup.outputChannels.parseString (xml.getStringAttribute ("audioDeviceOutChans", "11"), 2); | |||
| setup.useDefaultInputChannels = ! xml.hasAttribute ("audioDeviceInChans"); | |||
| setup.useDefaultOutputChannels = ! xml.hasAttribute ("audioDeviceOutChans"); | |||
| error = setAudioDeviceSetup (setup, true); | |||
| midiInsFromXml.clear(); | |||
| forEachXmlChildElementWithTagName (xml, c, "MIDIINPUT") | |||
| midiInsFromXml.add (c->getStringAttribute ("name")); | |||
| const StringArray allMidiIns (MidiInput::getDevices()); | |||
| for (int i = allMidiIns.size(); --i >= 0;) | |||
| setMidiInputEnabled (allMidiIns[i], midiInsFromXml.contains (allMidiIns[i])); | |||
| if (error.isNotEmpty() && selectDefaultDeviceOnFailure) | |||
| error = initialise (numInputChansNeeded, numOutputChansNeeded, | |||
| nullptr, false, preferredDefaultDeviceName); | |||
| setDefaultMidiOutput (xml.getStringAttribute ("defaultMidiOutput")); | |||
| return error; | |||
| } | |||
| String AudioDeviceManager::initialiseWithDefaultDevices (int numInputChannelsNeeded, | |||
| int numOutputChannelsNeeded) | |||
| { | |||
| lastExplicitSettings = nullptr; | |||
| return initialise (numInputChannelsNeeded, numOutputChannelsNeeded, | |||
| nullptr, false, String(), nullptr); | |||
| } | |||
| void AudioDeviceManager::insertDefaultDeviceNames (AudioDeviceSetup& setup) const | |||
| { | |||
| if (AudioIODeviceType* type = getCurrentDeviceTypeObject()) | |||
| { | |||
| if (setup.outputDeviceName.isEmpty()) | |||
| setup.outputDeviceName = type->getDeviceNames (false) [type->getDefaultDeviceIndex (false)]; | |||
| if (setup.inputDeviceName.isEmpty()) | |||
| setup.inputDeviceName = type->getDeviceNames (true) [type->getDefaultDeviceIndex (true)]; | |||
| } | |||
| } | |||
| XmlElement* AudioDeviceManager::createStateXml() const | |||
| { | |||
| return lastExplicitSettings.createCopy(); | |||
| } | |||
| //============================================================================== | |||
| void AudioDeviceManager::scanDevicesIfNeeded() | |||
| { | |||
| if (listNeedsScanning) | |||
| { | |||
| listNeedsScanning = false; | |||
| createDeviceTypesIfNeeded(); | |||
| for (int i = availableDeviceTypes.size(); --i >= 0;) | |||
| availableDeviceTypes.getUnchecked(i)->scanForDevices(); | |||
| } | |||
| } | |||
| AudioIODeviceType* AudioDeviceManager::findType (const String& typeName) | |||
| { | |||
| scanDevicesIfNeeded(); | |||
| for (int i = availableDeviceTypes.size(); --i >= 0;) | |||
| if (availableDeviceTypes.getUnchecked(i)->getTypeName() == typeName) | |||
| return availableDeviceTypes.getUnchecked(i); | |||
| return nullptr; | |||
| } | |||
| AudioIODeviceType* AudioDeviceManager::findType (const String& inputName, const String& outputName) | |||
| { | |||
| scanDevicesIfNeeded(); | |||
| for (int i = availableDeviceTypes.size(); --i >= 0;) | |||
| { | |||
| AudioIODeviceType* const type = availableDeviceTypes.getUnchecked(i); | |||
| if ((inputName.isNotEmpty() && type->getDeviceNames (true).contains (inputName, true)) | |||
| || (outputName.isNotEmpty() && type->getDeviceNames (false).contains (outputName, true))) | |||
| { | |||
| return type; | |||
| } | |||
| } | |||
| return nullptr; | |||
| } | |||
| void AudioDeviceManager::getAudioDeviceSetup (AudioDeviceSetup& setup) | |||
| { | |||
| setup = currentSetup; | |||
| } | |||
| void AudioDeviceManager::deleteCurrentDevice() | |||
| { | |||
| currentAudioDevice = nullptr; | |||
| currentSetup.inputDeviceName.clear(); | |||
| currentSetup.outputDeviceName.clear(); | |||
| } | |||
| void AudioDeviceManager::setCurrentAudioDeviceType (const String& type, | |||
| const bool treatAsChosenDevice) | |||
| { | |||
| for (int i = 0; i < availableDeviceTypes.size(); ++i) | |||
| { | |||
| if (availableDeviceTypes.getUnchecked(i)->getTypeName() == type | |||
| && currentDeviceType != type) | |||
| { | |||
| if (currentAudioDevice != nullptr) | |||
| { | |||
| closeAudioDevice(); | |||
| Thread::sleep (1500); // allow a moment for OS devices to sort themselves out, to help | |||
| // avoid things like DirectSound/ASIO clashes | |||
| } | |||
| currentDeviceType = type; | |||
| AudioDeviceSetup s (*lastDeviceTypeConfigs.getUnchecked(i)); | |||
| insertDefaultDeviceNames (s); | |||
| setAudioDeviceSetup (s, treatAsChosenDevice); | |||
| sendChangeMessage(); | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| AudioIODeviceType* AudioDeviceManager::getCurrentDeviceTypeObject() const | |||
| { | |||
| for (int i = 0; i < availableDeviceTypes.size(); ++i) | |||
| if (availableDeviceTypes.getUnchecked(i)->getTypeName() == currentDeviceType) | |||
| return availableDeviceTypes.getUnchecked(i); | |||
| return availableDeviceTypes[0]; | |||
| } | |||
| String AudioDeviceManager::setAudioDeviceSetup (const AudioDeviceSetup& newSetup, | |||
| const bool treatAsChosenDevice) | |||
| { | |||
| jassert (&newSetup != ¤tSetup); // this will have no effect | |||
| if (newSetup == currentSetup && currentAudioDevice != nullptr) | |||
| return String(); | |||
| if (! (newSetup == currentSetup)) | |||
| sendChangeMessage(); | |||
| stopDevice(); | |||
| const String newInputDeviceName (numInputChansNeeded == 0 ? String() : newSetup.inputDeviceName); | |||
| const String newOutputDeviceName (numOutputChansNeeded == 0 ? String() : newSetup.outputDeviceName); | |||
| String error; | |||
| AudioIODeviceType* type = getCurrentDeviceTypeObject(); | |||
| if (type == nullptr || (newInputDeviceName.isEmpty() && newOutputDeviceName.isEmpty())) | |||
| { | |||
| deleteCurrentDevice(); | |||
| if (treatAsChosenDevice) | |||
| updateXml(); | |||
| return String(); | |||
| } | |||
| if (currentSetup.inputDeviceName != newInputDeviceName | |||
| || currentSetup.outputDeviceName != newOutputDeviceName | |||
| || currentAudioDevice == nullptr) | |||
| { | |||
| deleteCurrentDevice(); | |||
| scanDevicesIfNeeded(); | |||
| if (newOutputDeviceName.isNotEmpty() | |||
| && ! type->getDeviceNames (false).contains (newOutputDeviceName)) | |||
| { | |||
| return "No such device: " + newOutputDeviceName; | |||
| } | |||
| if (newInputDeviceName.isNotEmpty() | |||
| && ! type->getDeviceNames (true).contains (newInputDeviceName)) | |||
| { | |||
| return "No such device: " + newInputDeviceName; | |||
| } | |||
| currentAudioDevice = type->createDevice (newOutputDeviceName, newInputDeviceName); | |||
| if (currentAudioDevice == nullptr) | |||
| error = "Can't open the audio device!\n\n" | |||
| "This may be because another application is currently using the same device - " | |||
| "if so, you should close any other applications and try again!"; | |||
| else | |||
| error = currentAudioDevice->getLastError(); | |||
| if (error.isNotEmpty()) | |||
| { | |||
| deleteCurrentDevice(); | |||
| return error; | |||
| } | |||
| if (newSetup.useDefaultInputChannels) | |||
| { | |||
| inputChannels.clear(); | |||
| inputChannels.setRange (0, numInputChansNeeded, true); | |||
| } | |||
| if (newSetup.useDefaultOutputChannels) | |||
| { | |||
| outputChannels.clear(); | |||
| outputChannels.setRange (0, numOutputChansNeeded, true); | |||
| } | |||
| if (newInputDeviceName.isEmpty()) inputChannels.clear(); | |||
| if (newOutputDeviceName.isEmpty()) outputChannels.clear(); | |||
| } | |||
| if (! newSetup.useDefaultInputChannels) inputChannels = newSetup.inputChannels; | |||
| if (! newSetup.useDefaultOutputChannels) outputChannels = newSetup.outputChannels; | |||
| currentSetup = newSetup; | |||
| currentSetup.sampleRate = chooseBestSampleRate (newSetup.sampleRate); | |||
| currentSetup.bufferSize = chooseBestBufferSize (newSetup.bufferSize); | |||
| error = currentAudioDevice->open (inputChannels, | |||
| outputChannels, | |||
| currentSetup.sampleRate, | |||
| currentSetup.bufferSize); | |||
| if (error.isEmpty()) | |||
| { | |||
| currentDeviceType = currentAudioDevice->getTypeName(); | |||
| currentAudioDevice->start (callbackHandler); | |||
| currentSetup.sampleRate = currentAudioDevice->getCurrentSampleRate(); | |||
| currentSetup.bufferSize = currentAudioDevice->getCurrentBufferSizeSamples(); | |||
| currentSetup.inputChannels = currentAudioDevice->getActiveInputChannels(); | |||
| currentSetup.outputChannels = currentAudioDevice->getActiveOutputChannels(); | |||
| for (int i = 0; i < availableDeviceTypes.size(); ++i) | |||
| if (availableDeviceTypes.getUnchecked (i)->getTypeName() == currentDeviceType) | |||
| *(lastDeviceTypeConfigs.getUnchecked (i)) = currentSetup; | |||
| if (treatAsChosenDevice) | |||
| updateXml(); | |||
| } | |||
| else | |||
| { | |||
| deleteCurrentDevice(); | |||
| } | |||
| return error; | |||
| } | |||
| double AudioDeviceManager::chooseBestSampleRate (double rate) const | |||
| { | |||
| jassert (currentAudioDevice != nullptr); | |||
| const Array<double> rates (currentAudioDevice->getAvailableSampleRates()); | |||
| if (rate > 0 && rates.contains (rate)) | |||
| return rate; | |||
| double lowestAbove44 = 0.0; | |||
| for (int i = rates.size(); --i >= 0;) | |||
| { | |||
| const double sr = rates[i]; | |||
| if (sr >= 44100.0 && (lowestAbove44 < 1.0 || sr < lowestAbove44)) | |||
| lowestAbove44 = sr; | |||
| } | |||
| if (lowestAbove44 > 0.0) | |||
| return lowestAbove44; | |||
| return rates[0]; | |||
| } | |||
| int AudioDeviceManager::chooseBestBufferSize (int bufferSize) const | |||
| { | |||
| jassert (currentAudioDevice != nullptr); | |||
| if (bufferSize > 0 && currentAudioDevice->getAvailableBufferSizes().contains (bufferSize)) | |||
| return bufferSize; | |||
| return currentAudioDevice->getDefaultBufferSize(); | |||
| } | |||
| void AudioDeviceManager::stopDevice() | |||
| { | |||
| if (currentAudioDevice != nullptr) | |||
| currentAudioDevice->stop(); | |||
| testSound = nullptr; | |||
| } | |||
| void AudioDeviceManager::closeAudioDevice() | |||
| { | |||
| stopDevice(); | |||
| currentAudioDevice = nullptr; | |||
| } | |||
| void AudioDeviceManager::restartLastAudioDevice() | |||
| { | |||
| if (currentAudioDevice == nullptr) | |||
| { | |||
| if (currentSetup.inputDeviceName.isEmpty() | |||
| && currentSetup.outputDeviceName.isEmpty()) | |||
| { | |||
| // This method will only reload the last device that was running | |||
| // before closeAudioDevice() was called - you need to actually open | |||
| // one first, with setAudioDevice(). | |||
| jassertfalse; | |||
| return; | |||
| } | |||
| AudioDeviceSetup s (currentSetup); | |||
| setAudioDeviceSetup (s, false); | |||
| } | |||
| } | |||
| void AudioDeviceManager::updateXml() | |||
| { | |||
| lastExplicitSettings = new XmlElement ("DEVICESETUP"); | |||
| lastExplicitSettings->setAttribute ("deviceType", currentDeviceType); | |||
| lastExplicitSettings->setAttribute ("audioOutputDeviceName", currentSetup.outputDeviceName); | |||
| lastExplicitSettings->setAttribute ("audioInputDeviceName", currentSetup.inputDeviceName); | |||
| if (currentAudioDevice != nullptr) | |||
| { | |||
| lastExplicitSettings->setAttribute ("audioDeviceRate", currentAudioDevice->getCurrentSampleRate()); | |||
| if (currentAudioDevice->getDefaultBufferSize() != currentAudioDevice->getCurrentBufferSizeSamples()) | |||
| lastExplicitSettings->setAttribute ("audioDeviceBufferSize", currentAudioDevice->getCurrentBufferSizeSamples()); | |||
| if (! currentSetup.useDefaultInputChannels) | |||
| lastExplicitSettings->setAttribute ("audioDeviceInChans", currentSetup.inputChannels.toString (2)); | |||
| if (! currentSetup.useDefaultOutputChannels) | |||
| lastExplicitSettings->setAttribute ("audioDeviceOutChans", currentSetup.outputChannels.toString (2)); | |||
| } | |||
| for (int i = 0; i < enabledMidiInputs.size(); ++i) | |||
| lastExplicitSettings->createNewChildElement ("MIDIINPUT") | |||
| ->setAttribute ("name", enabledMidiInputs[i]->getName()); | |||
| if (midiInsFromXml.size() > 0) | |||
| { | |||
| // Add any midi devices that have been enabled before, but which aren't currently | |||
| // open because the device has been disconnected. | |||
| const StringArray availableMidiDevices (MidiInput::getDevices()); | |||
| for (int i = 0; i < midiInsFromXml.size(); ++i) | |||
| if (! availableMidiDevices.contains (midiInsFromXml[i], true)) | |||
| lastExplicitSettings->createNewChildElement ("MIDIINPUT") | |||
| ->setAttribute ("name", midiInsFromXml[i]); | |||
| } | |||
| if (defaultMidiOutputName.isNotEmpty()) | |||
| lastExplicitSettings->setAttribute ("defaultMidiOutput", defaultMidiOutputName); | |||
| } | |||
| //============================================================================== | |||
| void AudioDeviceManager::addAudioCallback (AudioIODeviceCallback* newCallback) | |||
| { | |||
| { | |||
| const ScopedLock sl (audioCallbackLock); | |||
| if (callbacks.contains (newCallback)) | |||
| return; | |||
| } | |||
| if (currentAudioDevice != nullptr && newCallback != nullptr) | |||
| newCallback->audioDeviceAboutToStart (currentAudioDevice); | |||
| const ScopedLock sl (audioCallbackLock); | |||
| callbacks.add (newCallback); | |||
| } | |||
| void AudioDeviceManager::removeAudioCallback (AudioIODeviceCallback* callbackToRemove) | |||
| { | |||
| if (callbackToRemove != nullptr) | |||
| { | |||
| bool needsDeinitialising = currentAudioDevice != nullptr; | |||
| { | |||
| const ScopedLock sl (audioCallbackLock); | |||
| needsDeinitialising = needsDeinitialising && callbacks.contains (callbackToRemove); | |||
| callbacks.removeFirstMatchingValue (callbackToRemove); | |||
| } | |||
| if (needsDeinitialising) | |||
| callbackToRemove->audioDeviceStopped(); | |||
| } | |||
| } | |||
| void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelData, | |||
| int numInputChannels, | |||
| float** outputChannelData, | |||
| int numOutputChannels, | |||
| int numSamples) | |||
| { | |||
| const ScopedLock sl (audioCallbackLock); | |||
| if (inputLevelMeasurementEnabledCount.get() > 0 && numInputChannels > 0) | |||
| { | |||
| for (int j = 0; j < numSamples; ++j) | |||
| { | |||
| float s = 0; | |||
| for (int i = 0; i < numInputChannels; ++i) | |||
| s += std::abs (inputChannelData[i][j]); | |||
| s /= numInputChannels; | |||
| const double decayFactor = 0.99992; | |||
| if (s > inputLevel) | |||
| inputLevel = s; | |||
| else if (inputLevel > 0.001f) | |||
| inputLevel *= decayFactor; | |||
| else | |||
| inputLevel = 0; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| inputLevel = 0; | |||
| } | |||
| if (callbacks.size() > 0) | |||
| { | |||
| const double callbackStartTime = Time::getMillisecondCounterHiRes(); | |||
| tempBuffer.setSize (jmax (1, numOutputChannels), jmax (1, numSamples), false, false, true); | |||
| callbacks.getUnchecked(0)->audioDeviceIOCallback (inputChannelData, numInputChannels, | |||
| outputChannelData, numOutputChannels, numSamples); | |||
| float** const tempChans = tempBuffer.getArrayOfWritePointers(); | |||
| for (int i = callbacks.size(); --i > 0;) | |||
| { | |||
| callbacks.getUnchecked(i)->audioDeviceIOCallback (inputChannelData, numInputChannels, | |||
| tempChans, numOutputChannels, numSamples); | |||
| for (int chan = 0; chan < numOutputChannels; ++chan) | |||
| { | |||
| if (const float* const src = tempChans [chan]) | |||
| if (float* const dst = outputChannelData [chan]) | |||
| for (int j = 0; j < numSamples; ++j) | |||
| dst[j] += src[j]; | |||
| } | |||
| } | |||
| const double msTaken = Time::getMillisecondCounterHiRes() - callbackStartTime; | |||
| const double filterAmount = 0.2; | |||
| cpuUsageMs += filterAmount * (msTaken - cpuUsageMs); | |||
| } | |||
| else | |||
| { | |||
| for (int i = 0; i < numOutputChannels; ++i) | |||
| zeromem (outputChannelData[i], sizeof (float) * (size_t) numSamples); | |||
| } | |||
| if (testSound != nullptr) | |||
| { | |||
| const int numSamps = jmin (numSamples, testSound->getNumSamples() - testSoundPosition); | |||
| const float* const src = testSound->getReadPointer (0, testSoundPosition); | |||
| for (int i = 0; i < numOutputChannels; ++i) | |||
| for (int j = 0; j < numSamps; ++j) | |||
| outputChannelData [i][j] += src[j]; | |||
| testSoundPosition += numSamps; | |||
| if (testSoundPosition >= testSound->getNumSamples()) | |||
| testSound = nullptr; | |||
| } | |||
| } | |||
| void AudioDeviceManager::audioDeviceAboutToStartInt (AudioIODevice* const device) | |||
| { | |||
| cpuUsageMs = 0; | |||
| const double sampleRate = device->getCurrentSampleRate(); | |||
| const int blockSize = device->getCurrentBufferSizeSamples(); | |||
| if (sampleRate > 0.0 && blockSize > 0) | |||
| { | |||
| const double msPerBlock = 1000.0 * blockSize / sampleRate; | |||
| timeToCpuScale = (msPerBlock > 0.0) ? (1.0 / msPerBlock) : 0.0; | |||
| } | |||
| { | |||
| const ScopedLock sl (audioCallbackLock); | |||
| for (int i = callbacks.size(); --i >= 0;) | |||
| callbacks.getUnchecked(i)->audioDeviceAboutToStart (device); | |||
| } | |||
| sendChangeMessage(); | |||
| } | |||
| void AudioDeviceManager::audioDeviceStoppedInt() | |||
| { | |||
| cpuUsageMs = 0; | |||
| timeToCpuScale = 0; | |||
| sendChangeMessage(); | |||
| const ScopedLock sl (audioCallbackLock); | |||
| for (int i = callbacks.size(); --i >= 0;) | |||
| callbacks.getUnchecked(i)->audioDeviceStopped(); | |||
| } | |||
| void AudioDeviceManager::audioDeviceErrorInt (const String& message) | |||
| { | |||
| const ScopedLock sl (audioCallbackLock); | |||
| for (int i = callbacks.size(); --i >= 0;) | |||
| callbacks.getUnchecked(i)->audioDeviceError (message); | |||
| } | |||
| double AudioDeviceManager::getCpuUsage() const | |||
| { | |||
| return jlimit (0.0, 1.0, timeToCpuScale * cpuUsageMs); | |||
| } | |||
| //============================================================================== | |||
| void AudioDeviceManager::setMidiInputEnabled (const String& name, const bool enabled) | |||
| { | |||
| if (enabled != isMidiInputEnabled (name)) | |||
| { | |||
| if (enabled) | |||
| { | |||
| const int index = MidiInput::getDevices().indexOf (name); | |||
| if (index >= 0) | |||
| { | |||
| if (MidiInput* const midiIn = MidiInput::openDevice (index, callbackHandler)) | |||
| { | |||
| enabledMidiInputs.add (midiIn); | |||
| midiIn->start(); | |||
| } | |||
| } | |||
| } | |||
| else | |||
| { | |||
| for (int i = enabledMidiInputs.size(); --i >= 0;) | |||
| if (enabledMidiInputs[i]->getName() == name) | |||
| enabledMidiInputs.remove (i); | |||
| } | |||
| updateXml(); | |||
| sendChangeMessage(); | |||
| } | |||
| } | |||
| bool AudioDeviceManager::isMidiInputEnabled (const String& name) const | |||
| { | |||
| for (int i = enabledMidiInputs.size(); --i >= 0;) | |||
| if (enabledMidiInputs[i]->getName() == name) | |||
| return true; | |||
| return false; | |||
| } | |||
| void AudioDeviceManager::addMidiInputCallback (const String& name, MidiInputCallback* callbackToAdd) | |||
| { | |||
| removeMidiInputCallback (name, callbackToAdd); | |||
| if (name.isEmpty() || isMidiInputEnabled (name)) | |||
| { | |||
| const ScopedLock sl (midiCallbackLock); | |||
| MidiCallbackInfo mc; | |||
| mc.deviceName = name; | |||
| mc.callback = callbackToAdd; | |||
| midiCallbacks.add (mc); | |||
| } | |||
| } | |||
| void AudioDeviceManager::removeMidiInputCallback (const String& name, MidiInputCallback* callbackToRemove) | |||
| { | |||
| for (int i = midiCallbacks.size(); --i >= 0;) | |||
| { | |||
| const MidiCallbackInfo& mc = midiCallbacks.getReference(i); | |||
| if (mc.callback == callbackToRemove && mc.deviceName == name) | |||
| { | |||
| const ScopedLock sl (midiCallbackLock); | |||
| midiCallbacks.remove (i); | |||
| } | |||
| } | |||
| } | |||
| void AudioDeviceManager::handleIncomingMidiMessageInt (MidiInput* source, const MidiMessage& message) | |||
| { | |||
| if (! message.isActiveSense()) | |||
| { | |||
| const ScopedLock sl (midiCallbackLock); | |||
| for (int i = 0; i < midiCallbacks.size(); ++i) | |||
| { | |||
| const MidiCallbackInfo& mc = midiCallbacks.getReference(i); | |||
| if (mc.deviceName.isEmpty() || mc.deviceName == source->getName()) | |||
| mc.callback->handleIncomingMidiMessage (source, message); | |||
| } | |||
| } | |||
| } | |||
| //============================================================================== | |||
| void AudioDeviceManager::setDefaultMidiOutput (const String& deviceName) | |||
| { | |||
| if (defaultMidiOutputName != deviceName) | |||
| { | |||
| Array<AudioIODeviceCallback*> oldCallbacks; | |||
| { | |||
| const ScopedLock sl (audioCallbackLock); | |||
| oldCallbacks.swapWith (callbacks); | |||
| } | |||
| if (currentAudioDevice != nullptr) | |||
| for (int i = oldCallbacks.size(); --i >= 0;) | |||
| oldCallbacks.getUnchecked(i)->audioDeviceStopped(); | |||
| defaultMidiOutput = nullptr; | |||
| defaultMidiOutputName = deviceName; | |||
| if (deviceName.isNotEmpty()) | |||
| defaultMidiOutput = MidiOutput::openDevice (MidiOutput::getDevices().indexOf (deviceName)); | |||
| if (currentAudioDevice != nullptr) | |||
| for (int i = oldCallbacks.size(); --i >= 0;) | |||
| oldCallbacks.getUnchecked(i)->audioDeviceAboutToStart (currentAudioDevice); | |||
| { | |||
| const ScopedLock sl (audioCallbackLock); | |||
| oldCallbacks.swapWith (callbacks); | |||
| } | |||
| updateXml(); | |||
| sendChangeMessage(); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| void AudioDeviceManager::playTestSound() | |||
| { | |||
| { // cunningly nested to swap, unlock and delete in that order. | |||
| ScopedPointer<AudioSampleBuffer> oldSound; | |||
| { | |||
| const ScopedLock sl (audioCallbackLock); | |||
| oldSound = testSound; | |||
| } | |||
| } | |||
| testSoundPosition = 0; | |||
| if (currentAudioDevice != nullptr) | |||
| { | |||
| const double sampleRate = currentAudioDevice->getCurrentSampleRate(); | |||
| const int soundLength = (int) sampleRate; | |||
| const double frequency = 440.0; | |||
| const float amplitude = 0.5f; | |||
| const double phasePerSample = double_Pi * 2.0 / (sampleRate / frequency); | |||
| AudioSampleBuffer* const newSound = new AudioSampleBuffer (1, soundLength); | |||
| for (int i = 0; i < soundLength; ++i) | |||
| newSound->setSample (0, i, amplitude * (float) std::sin (i * phasePerSample)); | |||
| newSound->applyGainRamp (0, 0, soundLength / 10, 0.0f, 1.0f); | |||
| newSound->applyGainRamp (0, soundLength - soundLength / 4, soundLength / 4, 1.0f, 0.0f); | |||
| const ScopedLock sl (audioCallbackLock); | |||
| testSound = newSound; | |||
| } | |||
| } | |||
| void AudioDeviceManager::enableInputLevelMeasurement (const bool enableMeasurement) | |||
| { | |||
| if (enableMeasurement) | |||
| ++inputLevelMeasurementEnabledCount; | |||
| else | |||
| --inputLevelMeasurementEnabledCount; | |||
| inputLevel = 0; | |||
| } | |||
| double AudioDeviceManager::getCurrentInputLevel() const | |||
| { | |||
| jassert (inputLevelMeasurementEnabledCount.get() > 0); // you need to call enableInputLevelMeasurement() before using this! | |||
| return inputLevel; | |||
| } | |||
| @@ -1,512 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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_AUDIODEVICEMANAGER_H_INCLUDED | |||
| #define JUCE_AUDIODEVICEMANAGER_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| Manages the state of some audio and midi i/o devices. | |||
| This class keeps tracks of a currently-selected audio device, through | |||
| with which it continuously streams data from an audio callback, as well as | |||
| one or more midi inputs. | |||
| The idea is that your application will create one global instance of this object, | |||
| and let it take care of creating and deleting specific types of audio devices | |||
| internally. So when the device is changed, your callbacks will just keep running | |||
| without having to worry about this. | |||
| The manager can save and reload all of its device settings as XML, which | |||
| makes it very easy for you to save and reload the audio setup of your | |||
| application. | |||
| And to make it easy to let the user change its settings, there's a component | |||
| to do just that - the AudioDeviceSelectorComponent class, which contains a set of | |||
| device selection/sample-rate/latency controls. | |||
| To use an AudioDeviceManager, create one, and use initialise() to set it up. Then | |||
| call addAudioCallback() to register your audio callback with it, and use that to process | |||
| your audio data. | |||
| The manager also acts as a handy hub for incoming midi messages, allowing a | |||
| listener to register for messages from either a specific midi device, or from whatever | |||
| the current default midi input device is. The listener then doesn't have to worry about | |||
| re-registering with different midi devices if they are changed or deleted. | |||
| And yet another neat trick is that amount of CPU time being used is measured and | |||
| available with the getCpuUsage() method. | |||
| The AudioDeviceManager is a ChangeBroadcaster, and will send a change message to | |||
| listeners whenever one of its settings is changed. | |||
| @see AudioDeviceSelectorComponent, AudioIODevice, AudioIODeviceType | |||
| */ | |||
| class JUCE_API AudioDeviceManager : public ChangeBroadcaster | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates a default AudioDeviceManager. | |||
| Initially no audio device will be selected. You should call the initialise() method | |||
| and register an audio callback with setAudioCallback() before it'll be able to | |||
| actually make any noise. | |||
| */ | |||
| AudioDeviceManager(); | |||
| /** Destructor. */ | |||
| ~AudioDeviceManager(); | |||
| //============================================================================== | |||
| /** | |||
| This structure holds a set of properties describing the current audio setup. | |||
| An AudioDeviceManager uses this class to save/load its current settings, and to | |||
| specify your preferred options when opening a device. | |||
| @see AudioDeviceManager::setAudioDeviceSetup(), AudioDeviceManager::initialise() | |||
| */ | |||
| struct JUCE_API AudioDeviceSetup | |||
| { | |||
| /** Creates an AudioDeviceSetup object. | |||
| The default constructor sets all the member variables to indicate default values. | |||
| You can then fill-in any values you want to before passing the object to | |||
| AudioDeviceManager::initialise(). | |||
| */ | |||
| AudioDeviceSetup(); | |||
| bool operator== (const AudioDeviceSetup& other) const; | |||
| /** The name of the audio device used for output. | |||
| The name has to be one of the ones listed by the AudioDeviceManager's currently | |||
| selected device type. | |||
| This may be the same as the input device. | |||
| An empty string indicates the default device. | |||
| */ | |||
| String outputDeviceName; | |||
| /** The name of the audio device used for input. | |||
| This may be the same as the output device. | |||
| An empty string indicates the default device. | |||
| */ | |||
| String inputDeviceName; | |||
| /** The current sample rate. | |||
| This rate is used for both the input and output devices. | |||
| A value of 0 indicates that you don't care what rate is used, and the | |||
| device will choose a sensible rate for you. | |||
| */ | |||
| double sampleRate; | |||
| /** The buffer size, in samples. | |||
| This buffer size is used for both the input and output devices. | |||
| A value of 0 indicates the default buffer size. | |||
| */ | |||
| int bufferSize; | |||
| /** The set of active input channels. | |||
| The bits that are set in this array indicate the channels of the | |||
| input device that are active. | |||
| If useDefaultInputChannels is true, this value is ignored. | |||
| */ | |||
| BigInteger inputChannels; | |||
| /** If this is true, it indicates that the inputChannels array | |||
| should be ignored, and instead, the device's default channels | |||
| should be used. | |||
| */ | |||
| bool useDefaultInputChannels; | |||
| /** The set of active output channels. | |||
| The bits that are set in this array indicate the channels of the | |||
| input device that are active. | |||
| If useDefaultOutputChannels is true, this value is ignored. | |||
| */ | |||
| BigInteger outputChannels; | |||
| /** If this is true, it indicates that the outputChannels array | |||
| should be ignored, and instead, the device's default channels | |||
| should be used. | |||
| */ | |||
| bool useDefaultOutputChannels; | |||
| }; | |||
| //============================================================================== | |||
| /** Opens a set of audio devices ready for use. | |||
| This will attempt to open either a default audio device, or one that was | |||
| previously saved as XML. | |||
| @param numInputChannelsNeeded the maximum number of input channels your app would like to | |||
| use (the actual number of channels opened may be less than | |||
| the number requested) | |||
| @param numOutputChannelsNeeded the maximum number of output channels your app would like to | |||
| use (the actual number of channels opened may be less than | |||
| the number requested) | |||
| @param savedState either a previously-saved state that was produced | |||
| by createStateXml(), or nullptr if you want the manager | |||
| to choose the best device to open. | |||
| @param selectDefaultDeviceOnFailure if true, then if the device specified in the XML | |||
| fails to open, then a default device will be used | |||
| instead. If false, then on failure, no device is | |||
| opened. | |||
| @param preferredDefaultDeviceName if this is not empty, and there's a device with this | |||
| name, then that will be used as the default device | |||
| (assuming that there wasn't one specified in the XML). | |||
| The string can actually be a simple wildcard, containing "*" | |||
| and "?" characters | |||
| @param preferredSetupOptions if this is non-null, the structure will be used as the | |||
| set of preferred settings when opening the device. If you | |||
| use this parameter, the preferredDefaultDeviceName | |||
| field will be ignored | |||
| @returns an error message if anything went wrong, or an empty string if it worked ok. | |||
| */ | |||
| String initialise (int numInputChannelsNeeded, | |||
| int numOutputChannelsNeeded, | |||
| const XmlElement* savedState, | |||
| bool selectDefaultDeviceOnFailure, | |||
| const String& preferredDefaultDeviceName = String(), | |||
| const AudioDeviceSetup* preferredSetupOptions = nullptr); | |||
| /** Resets everything to a default device setup, clearing any stored settings. */ | |||
| String initialiseWithDefaultDevices (int numInputChannelsNeeded, | |||
| int numOutputChannelsNeeded); | |||
| /** Returns some XML representing the current state of the manager. | |||
| This stores the current device, its samplerate, block size, etc, and | |||
| can be restored later with initialise(). | |||
| Note that this can return a null pointer if no settings have been explicitly changed | |||
| (i.e. if the device manager has just been left in its default state). | |||
| */ | |||
| XmlElement* createStateXml() const; | |||
| //============================================================================== | |||
| /** Returns the current device properties that are in use. | |||
| @see setAudioDeviceSetup | |||
| */ | |||
| void getAudioDeviceSetup (AudioDeviceSetup& result); | |||
| /** Changes the current device or its settings. | |||
| If you want to change a device property, like the current sample rate or | |||
| block size, you can call getAudioDeviceSetup() to retrieve the current | |||
| settings, then tweak the appropriate fields in the AudioDeviceSetup structure, | |||
| and pass it back into this method to apply the new settings. | |||
| @param newSetup the settings that you'd like to use | |||
| @param treatAsChosenDevice if this is true and if the device opens correctly, these new | |||
| settings will be taken as having been explicitly chosen by the | |||
| user, and the next time createStateXml() is called, these settings | |||
| will be returned. If it's false, then the device is treated as a | |||
| temporary or default device, and a call to createStateXml() will | |||
| return either the last settings that were made with treatAsChosenDevice | |||
| as true, or the last XML settings that were passed into initialise(). | |||
| @returns an error message if anything went wrong, or an empty string if it worked ok. | |||
| @see getAudioDeviceSetup | |||
| */ | |||
| String setAudioDeviceSetup (const AudioDeviceSetup& newSetup, | |||
| bool treatAsChosenDevice); | |||
| /** Returns the currently-active audio device. */ | |||
| AudioIODevice* getCurrentAudioDevice() const noexcept { return currentAudioDevice; } | |||
| /** Returns the type of audio device currently in use. | |||
| @see setCurrentAudioDeviceType | |||
| */ | |||
| String getCurrentAudioDeviceType() const { return currentDeviceType; } | |||
| /** Returns the currently active audio device type object. | |||
| Don't keep a copy of this pointer - it's owned by the device manager and could | |||
| change at any time. | |||
| */ | |||
| AudioIODeviceType* getCurrentDeviceTypeObject() const; | |||
| /** Changes the class of audio device being used. | |||
| This switches between, e.g. ASIO and DirectSound. On the Mac you probably won't ever call | |||
| this because there's only one type: CoreAudio. | |||
| For a list of types, see getAvailableDeviceTypes(). | |||
| */ | |||
| void setCurrentAudioDeviceType (const String& type, | |||
| bool treatAsChosenDevice); | |||
| /** Closes the currently-open device. | |||
| You can call restartLastAudioDevice() later to reopen it in the same state | |||
| that it was just in. | |||
| */ | |||
| void closeAudioDevice(); | |||
| /** Tries to reload the last audio device that was running. | |||
| Note that this only reloads the last device that was running before | |||
| closeAudioDevice() was called - it doesn't reload any kind of saved-state, | |||
| and can only be called after a device has been opened with SetAudioDevice(). | |||
| If a device is already open, this call will do nothing. | |||
| */ | |||
| void restartLastAudioDevice(); | |||
| //============================================================================== | |||
| /** Registers an audio callback to be used. | |||
| The manager will redirect callbacks from whatever audio device is currently | |||
| in use to all registered callback objects. If more than one callback is | |||
| active, they will all be given the same input data, and their outputs will | |||
| be summed. | |||
| If necessary, this method will invoke audioDeviceAboutToStart() on the callback | |||
| object before returning. | |||
| To remove a callback, use removeAudioCallback(). | |||
| */ | |||
| void addAudioCallback (AudioIODeviceCallback* newCallback); | |||
| /** Deregisters a previously added callback. | |||
| If necessary, this method will invoke audioDeviceStopped() on the callback | |||
| object before returning. | |||
| @see addAudioCallback | |||
| */ | |||
| void removeAudioCallback (AudioIODeviceCallback* callback); | |||
| //============================================================================== | |||
| /** Returns the average proportion of available CPU being spent inside the audio callbacks. | |||
| @returns A value between 0 and 1.0 to indicate the approximate proportion of CPU | |||
| time spent in the callbacks. | |||
| */ | |||
| double getCpuUsage() const; | |||
| //============================================================================== | |||
| /** Enables or disables a midi input device. | |||
| The list of devices can be obtained with the MidiInput::getDevices() method. | |||
| Any incoming messages from enabled input devices will be forwarded on to all the | |||
| listeners that have been registered with the addMidiInputCallback() method. They | |||
| can either register for messages from a particular device, or from just the | |||
| "default" midi input. | |||
| Routing the midi input via an AudioDeviceManager means that when a listener | |||
| registers for the default midi input, this default device can be changed by the | |||
| manager without the listeners having to know about it or re-register. | |||
| It also means that a listener can stay registered for a midi input that is disabled | |||
| or not present, so that when the input is re-enabled, the listener will start | |||
| receiving messages again. | |||
| @see addMidiInputCallback, isMidiInputEnabled | |||
| */ | |||
| void setMidiInputEnabled (const String& midiInputDeviceName, bool enabled); | |||
| /** Returns true if a given midi input device is being used. | |||
| @see setMidiInputEnabled | |||
| */ | |||
| bool isMidiInputEnabled (const String& midiInputDeviceName) const; | |||
| /** Registers a listener for callbacks when midi events arrive from a midi input. | |||
| The device name can be empty to indicate that it wants to receive all incoming | |||
| events from all the enabled MIDI inputs. Or it can be the name of one of the | |||
| MIDI input devices if it just wants the events from that device. (see | |||
| MidiInput::getDevices() for the list of device names). | |||
| Only devices which are enabled (see the setMidiInputEnabled() method) will have their | |||
| events forwarded on to listeners. | |||
| */ | |||
| void addMidiInputCallback (const String& midiInputDeviceName, | |||
| MidiInputCallback* callback); | |||
| /** Removes a listener that was previously registered with addMidiInputCallback(). */ | |||
| void removeMidiInputCallback (const String& midiInputDeviceName, | |||
| MidiInputCallback* callback); | |||
| //============================================================================== | |||
| /** Sets a midi output device to use as the default. | |||
| The list of devices can be obtained with the MidiOutput::getDevices() method. | |||
| The specified device will be opened automatically and can be retrieved with the | |||
| getDefaultMidiOutput() method. | |||
| Pass in an empty string to deselect all devices. For the default device, you | |||
| can use MidiOutput::getDevices() [MidiOutput::getDefaultDeviceIndex()]. | |||
| @see getDefaultMidiOutput, getDefaultMidiOutputName | |||
| */ | |||
| void setDefaultMidiOutput (const String& deviceName); | |||
| /** Returns the name of the default midi output. | |||
| @see setDefaultMidiOutput, getDefaultMidiOutput | |||
| */ | |||
| const String& getDefaultMidiOutputName() const noexcept { return defaultMidiOutputName; } | |||
| /** Returns the current default midi output device. | |||
| If no device has been selected, or the device can't be opened, this will return nullptr. | |||
| @see getDefaultMidiOutputName | |||
| */ | |||
| MidiOutput* getDefaultMidiOutput() const noexcept { return defaultMidiOutput; } | |||
| /** Returns a list of the types of device supported. */ | |||
| const OwnedArray<AudioIODeviceType>& getAvailableDeviceTypes(); | |||
| //============================================================================== | |||
| /** Creates a list of available types. | |||
| This will add a set of new AudioIODeviceType objects to the specified list, to | |||
| represent each available types of device. | |||
| You can override this if your app needs to do something specific, like avoid | |||
| using DirectSound devices, etc. | |||
| */ | |||
| virtual void createAudioDeviceTypes (OwnedArray<AudioIODeviceType>& types); | |||
| /** Adds a new device type to the list of types. | |||
| The manager will take ownership of the object that is passed-in. | |||
| */ | |||
| void addAudioDeviceType (AudioIODeviceType* newDeviceType); | |||
| //============================================================================== | |||
| /** Plays a beep through the current audio device. | |||
| This is here to allow the audio setup UI panels to easily include a "test" | |||
| button so that the user can check where the audio is coming from. | |||
| */ | |||
| void playTestSound(); | |||
| /** Turns on level-measuring. | |||
| When enabled, the device manager will measure the peak input level | |||
| across all channels, and you can get this level by calling getCurrentInputLevel(). | |||
| This is mainly intended for audio setup UI panels to use to create a mic | |||
| level display, so that the user can check that they've selected the right | |||
| device. | |||
| A simple filter is used to make the level decay smoothly, but this is | |||
| only intended for giving rough feedback, and not for any kind of accurate | |||
| measurement. | |||
| */ | |||
| void enableInputLevelMeasurement (bool enableMeasurement); | |||
| /** Returns the current input level. | |||
| To use this, you must first enable it by calling enableInputLevelMeasurement(). | |||
| See enableInputLevelMeasurement() for more info. | |||
| */ | |||
| double getCurrentInputLevel() const; | |||
| /** Returns the a lock that can be used to synchronise access to the audio callback. | |||
| Obviously while this is locked, you're blocking the audio thread from running, so | |||
| it must only be used for very brief periods when absolutely necessary. | |||
| */ | |||
| CriticalSection& getAudioCallbackLock() noexcept { return audioCallbackLock; } | |||
| /** Returns the a lock that can be used to synchronise access to the midi callback. | |||
| Obviously while this is locked, you're blocking the midi system from running, so | |||
| it must only be used for very brief periods when absolutely necessary. | |||
| */ | |||
| CriticalSection& getMidiCallbackLock() noexcept { return midiCallbackLock; } | |||
| private: | |||
| //============================================================================== | |||
| OwnedArray<AudioIODeviceType> availableDeviceTypes; | |||
| OwnedArray<AudioDeviceSetup> lastDeviceTypeConfigs; | |||
| AudioDeviceSetup currentSetup; | |||
| ScopedPointer<AudioIODevice> currentAudioDevice; | |||
| Array<AudioIODeviceCallback*> callbacks; | |||
| int numInputChansNeeded, numOutputChansNeeded; | |||
| String currentDeviceType; | |||
| BigInteger inputChannels, outputChannels; | |||
| ScopedPointer<XmlElement> lastExplicitSettings; | |||
| mutable bool listNeedsScanning; | |||
| bool useInputNames; | |||
| Atomic<int> inputLevelMeasurementEnabledCount; | |||
| double inputLevel; | |||
| ScopedPointer<AudioSampleBuffer> testSound; | |||
| int testSoundPosition; | |||
| AudioSampleBuffer tempBuffer; | |||
| struct MidiCallbackInfo | |||
| { | |||
| String deviceName; | |||
| MidiInputCallback* callback; | |||
| }; | |||
| StringArray midiInsFromXml; | |||
| OwnedArray<MidiInput> enabledMidiInputs; | |||
| Array<MidiCallbackInfo> midiCallbacks; | |||
| String defaultMidiOutputName; | |||
| ScopedPointer<MidiOutput> defaultMidiOutput; | |||
| CriticalSection audioCallbackLock, midiCallbackLock; | |||
| double cpuUsageMs, timeToCpuScale; | |||
| //============================================================================== | |||
| class CallbackHandler; | |||
| friend class CallbackHandler; | |||
| friend struct ContainerDeletePolicy<CallbackHandler>; | |||
| ScopedPointer<CallbackHandler> callbackHandler; | |||
| void audioDeviceIOCallbackInt (const float** inputChannelData, int totalNumInputChannels, | |||
| float** outputChannelData, int totalNumOutputChannels, int numSamples); | |||
| void audioDeviceAboutToStartInt (AudioIODevice*); | |||
| void audioDeviceStoppedInt(); | |||
| void audioDeviceErrorInt (const String&); | |||
| void handleIncomingMidiMessageInt (MidiInput*, const MidiMessage&); | |||
| void audioDeviceListChanged(); | |||
| String restartDevice (int blockSizeToUse, double sampleRateToUse, | |||
| const BigInteger& ins, const BigInteger& outs); | |||
| void stopDevice(); | |||
| void updateXml(); | |||
| void createDeviceTypesIfNeeded(); | |||
| void scanDevicesIfNeeded(); | |||
| void deleteCurrentDevice(); | |||
| double chooseBestSampleRate (double preferred) const; | |||
| int chooseBestBufferSize (int preferred) const; | |||
| void insertDefaultDeviceNames (AudioDeviceSetup&) const; | |||
| String initialiseDefault (const String& preferredDefaultDeviceName, const AudioDeviceSetup*); | |||
| String initialiseFromXML (const XmlElement&, bool selectDefaultDeviceOnFailure, | |||
| const String& preferredDefaultDeviceName, const AudioDeviceSetup*); | |||
| AudioIODeviceType* findType (const String& inputName, const String& outputName); | |||
| AudioIODeviceType* findType (const String& typeName); | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioDeviceManager) | |||
| }; | |||
| #endif // JUCE_AUDIODEVICEMANAGER_H_INCLUDED | |||
| @@ -1,41 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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. | |||
| ============================================================================== | |||
| */ | |||
| AudioIODevice::AudioIODevice (const String& deviceName, const String& deviceTypeName) | |||
| : name (deviceName), typeName (deviceTypeName) | |||
| { | |||
| } | |||
| AudioIODevice::~AudioIODevice() {} | |||
| void AudioIODeviceCallback::audioDeviceError (const String&) {} | |||
| bool AudioIODevice::setAudioPreprocessingEnabled (bool) { return false; } | |||
| bool AudioIODevice::hasControlPanel() const { return false; } | |||
| bool AudioIODevice::showControlPanel() | |||
| { | |||
| jassertfalse; // this should only be called for devices which return true from | |||
| // their hasControlPanel() method. | |||
| return false; | |||
| } | |||
| @@ -1,309 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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_AUDIOIODEVICE_H_INCLUDED | |||
| #define JUCE_AUDIOIODEVICE_H_INCLUDED | |||
| class AudioIODevice; | |||
| //============================================================================== | |||
| /** | |||
| One of these is passed to an AudioIODevice object to stream the audio data | |||
| in and out. | |||
| The AudioIODevice will repeatedly call this class's audioDeviceIOCallback() | |||
| method on its own high-priority audio thread, when it needs to send or receive | |||
| the next block of data. | |||
| @see AudioIODevice, AudioDeviceManager | |||
| */ | |||
| class JUCE_API AudioIODeviceCallback | |||
| { | |||
| public: | |||
| /** Destructor. */ | |||
| virtual ~AudioIODeviceCallback() {} | |||
| /** Processes a block of incoming and outgoing audio data. | |||
| The subclass's implementation should use the incoming audio for whatever | |||
| purposes it needs to, and must fill all the output channels with the next | |||
| block of output data before returning. | |||
| The channel data is arranged with the same array indices as the channel name | |||
| array returned by AudioIODevice::getOutputChannelNames(), but those channels | |||
| that aren't specified in AudioIODevice::open() will have a null pointer for their | |||
| associated channel, so remember to check for this. | |||
| @param inputChannelData a set of arrays containing the audio data for each | |||
| incoming channel - this data is valid until the function | |||
| returns. There will be one channel of data for each input | |||
| channel that was enabled when the audio device was opened | |||
| (see AudioIODevice::open()) | |||
| @param numInputChannels the number of pointers to channel data in the | |||
| inputChannelData array. | |||
| @param outputChannelData a set of arrays which need to be filled with the data | |||
| that should be sent to each outgoing channel of the device. | |||
| There will be one channel of data for each output channel | |||
| that was enabled when the audio device was opened (see | |||
| AudioIODevice::open()) | |||
| The initial contents of the array is undefined, so the | |||
| callback function must fill all the channels with zeros if | |||
| its output is silence. Failing to do this could cause quite | |||
| an unpleasant noise! | |||
| @param numOutputChannels the number of pointers to channel data in the | |||
| outputChannelData array. | |||
| @param numSamples the number of samples in each channel of the input and | |||
| output arrays. The number of samples will depend on the | |||
| audio device's buffer size and will usually remain constant, | |||
| although this isn't guaranteed, so make sure your code can | |||
| cope with reasonable changes in the buffer size from one | |||
| callback to the next. | |||
| */ | |||
| virtual void audioDeviceIOCallback (const float** inputChannelData, | |||
| int numInputChannels, | |||
| float** outputChannelData, | |||
| int numOutputChannels, | |||
| int numSamples) = 0; | |||
| /** Called to indicate that the device is about to start calling back. | |||
| This will be called just before the audio callbacks begin, either when this | |||
| callback has just been added to an audio device, or after the device has been | |||
| restarted because of a sample-rate or block-size change. | |||
| You can use this opportunity to find out the sample rate and block size | |||
| that the device is going to use by calling the AudioIODevice::getCurrentSampleRate() | |||
| and AudioIODevice::getCurrentBufferSizeSamples() on the supplied pointer. | |||
| @param device the audio IO device that will be used to drive the callback. | |||
| Note that if you're going to store this this pointer, it is | |||
| only valid until the next time that audioDeviceStopped is called. | |||
| */ | |||
| virtual void audioDeviceAboutToStart (AudioIODevice* device) = 0; | |||
| /** Called to indicate that the device has stopped. */ | |||
| virtual void audioDeviceStopped() = 0; | |||
| /** This can be overridden to be told if the device generates an error while operating. | |||
| Be aware that this could be called by any thread! And not all devices perform | |||
| this callback. | |||
| */ | |||
| virtual void audioDeviceError (const String& errorMessage); | |||
| }; | |||
| //============================================================================== | |||
| /** | |||
| Base class for an audio device with synchronised input and output channels. | |||
| Subclasses of this are used to implement different protocols such as DirectSound, | |||
| ASIO, CoreAudio, etc. | |||
| To create one of these, you'll need to use the AudioIODeviceType class - see the | |||
| documentation for that class for more info. | |||
| For an easier way of managing audio devices and their settings, have a look at the | |||
| AudioDeviceManager class. | |||
| @see AudioIODeviceType, AudioDeviceManager | |||
| */ | |||
| class JUCE_API AudioIODevice | |||
| { | |||
| public: | |||
| /** Destructor. */ | |||
| virtual ~AudioIODevice(); | |||
| //============================================================================== | |||
| /** Returns the device's name, (as set in the constructor). */ | |||
| const String& getName() const noexcept { return name; } | |||
| /** Returns the type of the device. | |||
| E.g. "CoreAudio", "ASIO", etc. - this comes from the AudioIODeviceType that created it. | |||
| */ | |||
| const String& getTypeName() const noexcept { return typeName; } | |||
| //============================================================================== | |||
| /** Returns the names of all the available output channels on this device. | |||
| To find out which of these are currently in use, call getActiveOutputChannels(). | |||
| */ | |||
| virtual StringArray getOutputChannelNames() = 0; | |||
| /** Returns the names of all the available input channels on this device. | |||
| To find out which of these are currently in use, call getActiveInputChannels(). | |||
| */ | |||
| virtual StringArray getInputChannelNames() = 0; | |||
| //============================================================================== | |||
| /** Returns the set of sample-rates this device supports. | |||
| @see getCurrentSampleRate | |||
| */ | |||
| virtual Array<double> getAvailableSampleRates() = 0; | |||
| /** Returns the set of buffer sizes that are available. | |||
| @see getCurrentBufferSizeSamples, getDefaultBufferSize | |||
| */ | |||
| virtual Array<int> getAvailableBufferSizes() = 0; | |||
| /** Returns the default buffer-size to use. | |||
| @returns a number of samples | |||
| @see getAvailableBufferSizes | |||
| */ | |||
| virtual int getDefaultBufferSize() = 0; | |||
| //============================================================================== | |||
| /** Tries to open the device ready to play. | |||
| @param inputChannels a BigInteger in which a set bit indicates that the corresponding | |||
| input channel should be enabled | |||
| @param outputChannels a BigInteger in which a set bit indicates that the corresponding | |||
| output channel should be enabled | |||
| @param sampleRate the sample rate to try to use - to find out which rates are | |||
| available, see getAvailableSampleRates() | |||
| @param bufferSizeSamples the size of i/o buffer to use - to find out the available buffer | |||
| sizes, see getAvailableBufferSizes() | |||
| @returns an error description if there's a problem, or an empty string if it succeeds in | |||
| opening the device | |||
| @see close | |||
| */ | |||
| virtual String open (const BigInteger& inputChannels, | |||
| const BigInteger& outputChannels, | |||
| double sampleRate, | |||
| int bufferSizeSamples) = 0; | |||
| /** Closes and releases the device if it's open. */ | |||
| virtual void close() = 0; | |||
| /** Returns true if the device is still open. | |||
| A device might spontaneously close itself if something goes wrong, so this checks if | |||
| it's still open. | |||
| */ | |||
| virtual bool isOpen() = 0; | |||
| /** Starts the device actually playing. | |||
| This must be called after the device has been opened. | |||
| @param callback the callback to use for streaming the data. | |||
| @see AudioIODeviceCallback, open | |||
| */ | |||
| virtual void start (AudioIODeviceCallback* callback) = 0; | |||
| /** Stops the device playing. | |||
| Once a device has been started, this will stop it. Any pending calls to the | |||
| callback class will be flushed before this method returns. | |||
| */ | |||
| virtual void stop() = 0; | |||
| /** Returns true if the device is still calling back. | |||
| The device might mysteriously stop, so this checks whether it's | |||
| still playing. | |||
| */ | |||
| virtual bool isPlaying() = 0; | |||
| /** Returns the last error that happened if anything went wrong. */ | |||
| virtual String getLastError() = 0; | |||
| //============================================================================== | |||
| /** Returns the buffer size that the device is currently using. | |||
| If the device isn't actually open, this value doesn't really mean much. | |||
| */ | |||
| virtual int getCurrentBufferSizeSamples() = 0; | |||
| /** Returns the sample rate that the device is currently using. | |||
| If the device isn't actually open, this value doesn't really mean much. | |||
| */ | |||
| virtual double getCurrentSampleRate() = 0; | |||
| /** Returns the device's current physical bit-depth. | |||
| If the device isn't actually open, this value doesn't really mean much. | |||
| */ | |||
| virtual int getCurrentBitDepth() = 0; | |||
| /** Returns a mask showing which of the available output channels are currently | |||
| enabled. | |||
| @see getOutputChannelNames | |||
| */ | |||
| virtual BigInteger getActiveOutputChannels() const = 0; | |||
| /** Returns a mask showing which of the available input channels are currently | |||
| enabled. | |||
| @see getInputChannelNames | |||
| */ | |||
| virtual BigInteger getActiveInputChannels() const = 0; | |||
| /** Returns the device's output latency. | |||
| This is the delay in samples between a callback getting a block of data, and | |||
| that data actually getting played. | |||
| */ | |||
| virtual int getOutputLatencyInSamples() = 0; | |||
| /** Returns the device's input latency. | |||
| This is the delay in samples between some audio actually arriving at the soundcard, | |||
| and the callback getting passed this block of data. | |||
| */ | |||
| virtual int getInputLatencyInSamples() = 0; | |||
| //============================================================================== | |||
| /** True if this device can show a pop-up control panel for editing its settings. | |||
| This is generally just true of ASIO devices. If true, you can call showControlPanel() | |||
| to display it. | |||
| */ | |||
| virtual bool hasControlPanel() const; | |||
| /** Shows a device-specific control panel if there is one. | |||
| This should only be called for devices which return true from hasControlPanel(). | |||
| */ | |||
| virtual bool showControlPanel(); | |||
| /** On devices which support it, this allows automatic gain control or other | |||
| mic processing to be disabled. | |||
| If the device doesn't support this operation, it'll return false. | |||
| */ | |||
| virtual bool setAudioPreprocessingEnabled (bool shouldBeEnabled); | |||
| //============================================================================== | |||
| protected: | |||
| /** Creates a device, setting its name and type member variables. */ | |||
| AudioIODevice (const String& deviceName, | |||
| const String& typeName); | |||
| /** @internal */ | |||
| String name, typeName; | |||
| }; | |||
| #endif // JUCE_AUDIOIODEVICE_H_INCLUDED | |||
| @@ -1,78 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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. | |||
| ============================================================================== | |||
| */ | |||
| AudioIODeviceType::AudioIODeviceType (const String& name) | |||
| : typeName (name) | |||
| { | |||
| } | |||
| AudioIODeviceType::~AudioIODeviceType() | |||
| { | |||
| } | |||
| //============================================================================== | |||
| void AudioIODeviceType::addListener (Listener* l) { listeners.add (l); } | |||
| void AudioIODeviceType::removeListener (Listener* l) { listeners.remove (l); } | |||
| void AudioIODeviceType::callDeviceChangeListeners() | |||
| { | |||
| listeners.call (&AudioIODeviceType::Listener::audioDeviceListChanged); | |||
| } | |||
| //============================================================================== | |||
| #if ! JUCE_MAC | |||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_CoreAudio() { return nullptr; } | |||
| #endif | |||
| #if ! JUCE_IOS | |||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio() { return nullptr; } | |||
| #endif | |||
| #if ! (JUCE_WINDOWS && JUCE_WASAPI) | |||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI() { return nullptr; } | |||
| #endif | |||
| #if ! (JUCE_WINDOWS && JUCE_DIRECTSOUND) | |||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_DirectSound() { return nullptr; } | |||
| #endif | |||
| #if ! (JUCE_WINDOWS && JUCE_ASIO) | |||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ASIO() { return nullptr; } | |||
| #endif | |||
| #if ! (JUCE_LINUX && JUCE_ALSA) | |||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ALSA() { return nullptr; } | |||
| #endif | |||
| #if ! (JUCE_LINUX && JUCE_JACK) | |||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK() { return nullptr; } | |||
| #endif | |||
| #if ! JUCE_ANDROID | |||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android() { return nullptr; } | |||
| #endif | |||
| #if ! (JUCE_ANDROID && JUCE_USE_ANDROID_OPENSLES) | |||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES() { return nullptr; } | |||
| #endif | |||
| @@ -1,182 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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_AUDIOIODEVICETYPE_H_INCLUDED | |||
| #define JUCE_AUDIOIODEVICETYPE_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| Represents a type of audio driver, such as DirectSound, ASIO, CoreAudio, etc. | |||
| To get a list of available audio driver types, use the AudioDeviceManager::createAudioDeviceTypes() | |||
| method. Each of the objects returned can then be used to list the available | |||
| devices of that type. E.g. | |||
| @code | |||
| OwnedArray<AudioIODeviceType> types; | |||
| myAudioDeviceManager.createAudioDeviceTypes (types); | |||
| for (int i = 0; i < types.size(); ++i) | |||
| { | |||
| String typeName (types[i]->getTypeName()); // This will be things like "DirectSound", "CoreAudio", etc. | |||
| types[i]->scanForDevices(); // This must be called before getting the list of devices | |||
| StringArray deviceNames (types[i]->getDeviceNames()); // This will now return a list of available devices of this type | |||
| for (int j = 0; j < deviceNames.size(); ++j) | |||
| { | |||
| AudioIODevice* device = types[i]->createDevice (deviceNames [j]); | |||
| ... | |||
| } | |||
| } | |||
| @endcode | |||
| For an easier way of managing audio devices and their settings, have a look at the | |||
| AudioDeviceManager class. | |||
| @see AudioIODevice, AudioDeviceManager | |||
| */ | |||
| class JUCE_API AudioIODeviceType | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Returns the name of this type of driver that this object manages. | |||
| This will be something like "DirectSound", "ASIO", "CoreAudio", "ALSA", etc. | |||
| */ | |||
| const String& getTypeName() const noexcept { return typeName; } | |||
| //============================================================================== | |||
| /** Refreshes the object's cached list of known devices. | |||
| This must be called at least once before calling getDeviceNames() or any of | |||
| the other device creation methods. | |||
| */ | |||
| virtual void scanForDevices() = 0; | |||
| /** Returns the list of available devices of this type. | |||
| The scanForDevices() method must have been called to create this list. | |||
| @param wantInputNames only really used by DirectSound where devices are split up | |||
| into inputs and outputs, this indicates whether to use | |||
| the input or output name to refer to a pair of devices. | |||
| */ | |||
| virtual StringArray getDeviceNames (bool wantInputNames = false) const = 0; | |||
| /** Returns the name of the default device. | |||
| This will be one of the names from the getDeviceNames() list. | |||
| @param forInput if true, this means that a default input device should be | |||
| returned; if false, it should return the default output | |||
| */ | |||
| virtual int getDefaultDeviceIndex (bool forInput) const = 0; | |||
| /** Returns the index of a given device in the list of device names. | |||
| If asInput is true, it shows the index in the inputs list, otherwise it | |||
| looks for it in the outputs list. | |||
| */ | |||
| virtual int getIndexOfDevice (AudioIODevice* device, bool asInput) const = 0; | |||
| /** Returns true if two different devices can be used for the input and output. | |||
| */ | |||
| virtual bool hasSeparateInputsAndOutputs() const = 0; | |||
| /** Creates one of the devices of this type. | |||
| The deviceName must be one of the strings returned by getDeviceNames(), and | |||
| scanForDevices() must have been called before this method is used. | |||
| */ | |||
| virtual AudioIODevice* createDevice (const String& outputDeviceName, | |||
| const String& inputDeviceName) = 0; | |||
| //============================================================================== | |||
| /** | |||
| A class for receiving events when audio devices are inserted or removed. | |||
| You can register an AudioIODeviceType::Listener with an~AudioIODeviceType object | |||
| using the AudioIODeviceType::addListener() method, and it will be called when | |||
| devices of that type are added or removed. | |||
| @see AudioIODeviceType::addListener, AudioIODeviceType::removeListener | |||
| */ | |||
| class Listener | |||
| { | |||
| public: | |||
| virtual ~Listener() {} | |||
| /** Called when the list of available audio devices changes. */ | |||
| virtual void audioDeviceListChanged() = 0; | |||
| }; | |||
| /** Adds a listener that will be called when this type of device is added or | |||
| removed from the system. | |||
| */ | |||
| void addListener (Listener* listener); | |||
| /** Removes a listener that was previously added with addListener(). */ | |||
| void removeListener (Listener* listener); | |||
| //============================================================================== | |||
| /** Destructor. */ | |||
| virtual ~AudioIODeviceType(); | |||
| //============================================================================== | |||
| /** Creates a CoreAudio device type if it's available on this platform, or returns null. */ | |||
| static AudioIODeviceType* createAudioIODeviceType_CoreAudio(); | |||
| /** Creates an iOS device type if it's available on this platform, or returns null. */ | |||
| static AudioIODeviceType* createAudioIODeviceType_iOSAudio(); | |||
| /** Creates a WASAPI device type if it's available on this platform, or returns null. */ | |||
| static AudioIODeviceType* createAudioIODeviceType_WASAPI(); | |||
| /** Creates a DirectSound device type if it's available on this platform, or returns null. */ | |||
| static AudioIODeviceType* createAudioIODeviceType_DirectSound(); | |||
| /** Creates an ASIO device type if it's available on this platform, or returns null. */ | |||
| static AudioIODeviceType* createAudioIODeviceType_ASIO(); | |||
| /** Creates an ALSA device type if it's available on this platform, or returns null. */ | |||
| static AudioIODeviceType* createAudioIODeviceType_ALSA(); | |||
| /** Creates a JACK device type if it's available on this platform, or returns null. */ | |||
| static AudioIODeviceType* createAudioIODeviceType_JACK(); | |||
| /** Creates an Android device type if it's available on this platform, or returns null. */ | |||
| static AudioIODeviceType* createAudioIODeviceType_Android(); | |||
| /** Creates an Android OpenSLES device type if it's available on this platform, or returns null. */ | |||
| static AudioIODeviceType* createAudioIODeviceType_OpenSLES(); | |||
| protected: | |||
| explicit AudioIODeviceType (const String& typeName); | |||
| /** Synchronously calls all the registered device list change listeners. */ | |||
| void callDeviceChangeListeners(); | |||
| private: | |||
| String typeName; | |||
| ListenerList<Listener> listeners; | |||
| JUCE_DECLARE_NON_COPYABLE (AudioIODeviceType) | |||
| }; | |||
| #endif // JUCE_AUDIOIODEVICETYPE_H_INCLUDED | |||
| @@ -1,61 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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_SYSTEMAUDIOVOLUME_H_INCLUDED | |||
| #define JUCE_SYSTEMAUDIOVOLUME_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| Contains functions to control the system's master volume. | |||
| */ | |||
| class JUCE_API SystemAudioVolume | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Returns the operating system's current volume level in the range 0 to 1.0 */ | |||
| static float JUCE_CALLTYPE getGain(); | |||
| /** Attempts to set the operating system's current volume level. | |||
| @param newGain the level, between 0 and 1.0 | |||
| @returns true if the operation succeeds | |||
| */ | |||
| static bool JUCE_CALLTYPE setGain (float newGain); | |||
| /** Returns true if the system's audio output is currently muted. */ | |||
| static bool JUCE_CALLTYPE isMuted(); | |||
| /** Attempts to mute the operating system's audio output. | |||
| @param shouldBeMuted true if you want it to be muted | |||
| @returns true if the operation succeeds | |||
| */ | |||
| static bool JUCE_CALLTYPE setMuted (bool shouldBeMuted); | |||
| private: | |||
| SystemAudioVolume(); // Don't instantiate this class, just call its static fns. | |||
| JUCE_DECLARE_NON_COPYABLE (SystemAudioVolume) | |||
| }; | |||
| #endif // JUCE_SYSTEMAUDIOVOLUME_H_INCLUDED | |||
| @@ -1,227 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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_DEVICES_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_core/native/juce_BasicNativeHeaders.h" | |||
| #include "juce_audio_devices.h" | |||
| //============================================================================== | |||
| #if JUCE_MAC | |||
| #define Point CarbonDummyPointName | |||
| #define Component CarbonDummyCompName | |||
| #import <CoreAudio/AudioHardware.h> | |||
| #import <CoreMIDI/MIDIServices.h> | |||
| #import <DiscRecording/DiscRecording.h> | |||
| #import <AudioToolbox/AudioServices.h> | |||
| #undef Point | |||
| #undef Component | |||
| #elif JUCE_IOS | |||
| #import <AudioToolbox/AudioToolbox.h> | |||
| #import <AVFoundation/AVFoundation.h> | |||
| #import <CoreMIDI/MIDIServices.h> | |||
| //============================================================================== | |||
| #elif JUCE_WINDOWS | |||
| #if JUCE_WASAPI | |||
| #include <MMReg.h> | |||
| #endif | |||
| #if JUCE_ASIO | |||
| /* This is very frustrating - we only need to use a handful of definitions from | |||
| a couple of the header files in Steinberg's ASIO SDK, and it'd be easy to copy | |||
| about 30 lines of code into this cpp file to create a fully stand-alone ASIO | |||
| implementation... | |||
| ..unfortunately that would break Steinberg's license agreement for use of | |||
| their SDK, so I'm not allowed to do this. | |||
| This means that anyone who wants to use JUCE's ASIO abilities will have to: | |||
| 1) Agree to Steinberg's licensing terms and download the ASIO SDK | |||
| (see www.steinberg.net/Steinberg/Developers.asp). | |||
| 2) Enable this code with a global definition #define JUCE_ASIO 1. | |||
| 3) Make sure that your header search path contains the iasiodrv.h file that | |||
| comes with the SDK. (Only about a handful of the SDK header files are actually | |||
| needed - so to simplify things, you could just copy these into your JUCE directory). | |||
| */ | |||
| #include <iasiodrv.h> | |||
| #endif | |||
| #if JUCE_USE_CDBURNER | |||
| /* You'll need the Platform SDK for these headers - if you don't have it and don't | |||
| need to use CD-burning, then you might just want to set the JUCE_USE_CDBURNER flag | |||
| to 0, to avoid these includes. | |||
| */ | |||
| #include <imapi.h> | |||
| #include <imapierror.h> | |||
| #endif | |||
| //============================================================================== | |||
| #elif JUCE_LINUX | |||
| #if JUCE_ALSA | |||
| /* Got an include error here? If so, you've either not got ALSA installed, or you've | |||
| not got your paths set up correctly to find its header files. | |||
| The package you need to install to get ASLA support is "libasound2-dev". | |||
| If you don't have the ALSA library and don't want to build Juce with audio support, | |||
| just set the JUCE_ALSA flag to 0. | |||
| */ | |||
| #include <alsa/asoundlib.h> | |||
| #endif | |||
| #if JUCE_JACK | |||
| /* Got an include error here? If so, you've either not got jack-audio-connection-kit | |||
| installed, or you've not got your paths set up correctly to find its header files. | |||
| The package you need to install to get JACK support is "libjack-dev". | |||
| If you don't have the jack-audio-connection-kit library and don't want to build | |||
| Juce with low latency audio support, just set the JUCE_JACK flag to 0. | |||
| */ | |||
| #include <jack/jack.h> | |||
| #endif | |||
| #undef SIZEOF | |||
| //============================================================================== | |||
| #elif JUCE_ANDROID | |||
| #if JUCE_USE_ANDROID_OPENSLES | |||
| #include <SLES/OpenSLES.h> | |||
| #include <SLES/OpenSLES_Android.h> | |||
| #include <SLES/OpenSLES_AndroidConfiguration.h> | |||
| #endif | |||
| #endif | |||
| namespace juce | |||
| { | |||
| #include "audio_io/juce_AudioDeviceManager.cpp" | |||
| #include "audio_io/juce_AudioIODevice.cpp" | |||
| #include "audio_io/juce_AudioIODeviceType.cpp" | |||
| #include "midi_io/juce_MidiMessageCollector.cpp" | |||
| #include "midi_io/juce_MidiOutput.cpp" | |||
| #include "audio_cd/juce_AudioCDReader.cpp" | |||
| #include "sources/juce_AudioSourcePlayer.cpp" | |||
| #include "sources/juce_AudioTransportSource.cpp" | |||
| #include "native/juce_MidiDataConcatenator.h" | |||
| //============================================================================== | |||
| #if JUCE_MAC | |||
| #include "../juce_core/native/juce_osx_ObjCHelpers.h" | |||
| #include "native/juce_mac_CoreAudio.cpp" | |||
| #include "native/juce_mac_CoreMidi.cpp" | |||
| #if JUCE_USE_CDREADER | |||
| #include "native/juce_mac_AudioCDReader.mm" | |||
| #endif | |||
| #if JUCE_USE_CDBURNER | |||
| #include "native/juce_mac_AudioCDBurner.mm" | |||
| #endif | |||
| //============================================================================== | |||
| #elif JUCE_IOS | |||
| #include "native/juce_ios_Audio.cpp" | |||
| #include "native/juce_mac_CoreMidi.cpp" | |||
| //============================================================================== | |||
| #elif JUCE_WINDOWS | |||
| #include "../juce_core/native/juce_win32_ComSmartPtr.h" | |||
| #include "../juce_events/native/juce_win32_HiddenMessageWindow.h" | |||
| #if JUCE_WASAPI | |||
| #include "native/juce_win32_WASAPI.cpp" | |||
| #endif | |||
| #if JUCE_DIRECTSOUND | |||
| #include "native/juce_win32_DirectSound.cpp" | |||
| #endif | |||
| #include "native/juce_win32_Midi.cpp" | |||
| #if JUCE_ASIO | |||
| #include "native/juce_win32_ASIO.cpp" | |||
| #endif | |||
| #if JUCE_USE_CDREADER | |||
| #include "native/juce_win32_AudioCDReader.cpp" | |||
| #endif | |||
| #if JUCE_USE_CDBURNER | |||
| #include "native/juce_win32_AudioCDBurner.cpp" | |||
| #endif | |||
| //============================================================================== | |||
| #elif JUCE_LINUX | |||
| #if JUCE_ALSA | |||
| #include "native/juce_linux_ALSA.cpp" | |||
| #endif | |||
| #include "native/juce_linux_Midi.cpp" | |||
| #if JUCE_JACK | |||
| #include "native/juce_linux_JackAudio.cpp" | |||
| #endif | |||
| #if JUCE_USE_CDREADER | |||
| #include "native/juce_linux_AudioCDReader.cpp" | |||
| #endif | |||
| //============================================================================== | |||
| #elif JUCE_ANDROID | |||
| #include "../juce_core/native/juce_android_JNIHelpers.h" | |||
| #include "native/juce_android_Audio.cpp" | |||
| #include "native/juce_android_Midi.cpp" | |||
| #if JUCE_USE_ANDROID_OPENSLES | |||
| #include "native/juce_android_OpenSL.cpp" | |||
| #endif | |||
| #endif | |||
| #if ! JUCE_SYSTEMAUDIOVOL_IMPLEMENTED | |||
| // None of these methods are available. (On Windows you might need to enable WASAPI for this) | |||
| float JUCE_CALLTYPE SystemAudioVolume::getGain() { jassertfalse; return 0.0f; } | |||
| bool JUCE_CALLTYPE SystemAudioVolume::setGain (float) { jassertfalse; return false; } | |||
| bool JUCE_CALLTYPE SystemAudioVolume::isMuted() { jassertfalse; return false; } | |||
| bool JUCE_CALLTYPE SystemAudioVolume::setMuted (bool) { jassertfalse; return false; } | |||
| #endif | |||
| } | |||
| @@ -1,117 +1,5 @@ | |||
| /* | |||
| ============================================================================== | |||
| // This is an auto-generated file to redirect any included | |||
| // module headers to the correct external folder. | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2013 - Raw Material Software Ltd. | |||
| #include "../../../../../modules/juce_audio_devices/juce_audio_devices.h" | |||
| 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_DEVICES_H_INCLUDED | |||
| #define JUCE_AUDIO_DEVICES_H_INCLUDED | |||
| #include "../juce_events/juce_events.h" | |||
| #include "../juce_audio_basics/juce_audio_basics.h" | |||
| #include "../juce_audio_formats/juce_audio_formats.h" | |||
| //============================================================================= | |||
| /** Config: JUCE_ASIO | |||
| Enables ASIO audio devices (MS Windows only). | |||
| Turning this on means that you'll need to have the Steinberg ASIO SDK installed | |||
| on your Windows build machine. | |||
| See the comments in the ASIOAudioIODevice class's header file for more | |||
| info about this. | |||
| */ | |||
| #ifndef JUCE_ASIO | |||
| #define JUCE_ASIO 0 | |||
| #endif | |||
| /** Config: JUCE_WASAPI | |||
| Enables WASAPI audio devices (Windows Vista and above). | |||
| */ | |||
| #ifndef JUCE_WASAPI | |||
| #define JUCE_WASAPI 1 | |||
| #endif | |||
| /** Config: JUCE_DIRECTSOUND | |||
| Enables DirectSound audio (MS Windows only). | |||
| */ | |||
| #ifndef JUCE_DIRECTSOUND | |||
| #define JUCE_DIRECTSOUND 1 | |||
| #endif | |||
| /** Config: JUCE_ALSA | |||
| Enables ALSA audio devices (Linux only). | |||
| */ | |||
| #ifndef JUCE_ALSA | |||
| #define JUCE_ALSA 1 | |||
| #endif | |||
| /** Config: JUCE_JACK | |||
| Enables JACK audio devices (Linux only). | |||
| */ | |||
| #ifndef JUCE_JACK | |||
| #define JUCE_JACK 0 | |||
| #endif | |||
| /** Config: JUCE_USE_ANDROID_OPENSLES | |||
| Enables OpenSLES devices (Android only). | |||
| */ | |||
| #ifndef JUCE_USE_ANDROID_OPENSLES | |||
| #if JUCE_ANDROID_API_VERSION > 8 | |||
| #define JUCE_USE_ANDROID_OPENSLES 1 | |||
| #else | |||
| #define JUCE_USE_ANDROID_OPENSLES 0 | |||
| #endif | |||
| #endif | |||
| //============================================================================= | |||
| /** Config: JUCE_USE_CDREADER | |||
| Enables the AudioCDReader class (on supported platforms). | |||
| */ | |||
| #ifndef JUCE_USE_CDREADER | |||
| #define JUCE_USE_CDREADER 0 | |||
| #endif | |||
| /** Config: JUCE_USE_CDBURNER | |||
| Enables the AudioCDBurner class (on supported platforms). | |||
| */ | |||
| #ifndef JUCE_USE_CDBURNER | |||
| #define JUCE_USE_CDBURNER 0 | |||
| #endif | |||
| //============================================================================= | |||
| namespace juce | |||
| { | |||
| #include "audio_io/juce_AudioIODevice.h" | |||
| #include "audio_io/juce_AudioIODeviceType.h" | |||
| #include "audio_io/juce_SystemAudioVolume.h" | |||
| #include "midi_io/juce_MidiInput.h" | |||
| #include "midi_io/juce_MidiMessageCollector.h" | |||
| #include "midi_io/juce_MidiOutput.h" | |||
| #include "sources/juce_AudioSourcePlayer.h" | |||
| #include "sources/juce_AudioTransportSource.h" | |||
| #include "audio_cd/juce_AudioCDBurner.h" | |||
| #include "audio_cd/juce_AudioCDReader.h" | |||
| #include "audio_io/juce_AudioDeviceManager.h" | |||
| } | |||
| #endif // JUCE_AUDIO_DEVICES_H_INCLUDED | |||
| @@ -1,25 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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_devices.cpp" | |||
| @@ -1,28 +0,0 @@ | |||
| { | |||
| "id": "juce_audio_devices", | |||
| "name": "JUCE audio and midi I/O device classes", | |||
| "version": "3.0.8", | |||
| "description": "Classes to play and record from audio and midi i/o devices.", | |||
| "website": "http://www.juce.com/juce", | |||
| "license": "GPL/Commercial", | |||
| "dependencies": [ { "id": "juce_audio_basics", "version": "matching" }, | |||
| { "id": "juce_audio_formats", "version": "matching" }, | |||
| { "id": "juce_events", "version": "matching" } ], | |||
| "include": "juce_audio_devices.h", | |||
| "compile": [ { "file": "juce_audio_devices.cpp", "target": "! xcode" }, | |||
| { "file": "juce_audio_devices.mm", "target": "xcode" } ], | |||
| "browse": [ "audio_io/*", | |||
| "midi_io/*", | |||
| "sources/*", | |||
| "audio_cd/*", | |||
| "native/*" ], | |||
| "OSXFrameworks": "CoreAudio CoreMIDI DiscRecording", | |||
| "iOSFrameworks": "AudioToolbox CoreMIDI", | |||
| "LinuxLibs": "asound", | |||
| "mingwLibs": "winmm" | |||
| } | |||
| @@ -1,182 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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_MIDIINPUT_H_INCLUDED | |||
| #define JUCE_MIDIINPUT_H_INCLUDED | |||
| class MidiInput; | |||
| //============================================================================== | |||
| /** | |||
| Receives incoming messages from a physical MIDI input device. | |||
| This class is overridden to handle incoming midi messages. See the MidiInput | |||
| class for more details. | |||
| @see MidiInput | |||
| */ | |||
| class JUCE_API MidiInputCallback | |||
| { | |||
| public: | |||
| /** Destructor. */ | |||
| virtual ~MidiInputCallback() {} | |||
| /** Receives an incoming message. | |||
| A MidiInput object will call this method when a midi event arrives. It'll be | |||
| called on a high-priority system thread, so avoid doing anything time-consuming | |||
| in here, and avoid making any UI calls. You might find the MidiBuffer class helpful | |||
| for queueing incoming messages for use later. | |||
| @param source the MidiInput object that generated the message | |||
| @param message the incoming message. The message's timestamp is set to a value | |||
| equivalent to (Time::getMillisecondCounter() / 1000.0) to specify the | |||
| time when the message arrived. | |||
| */ | |||
| virtual void handleIncomingMidiMessage (MidiInput* source, | |||
| const MidiMessage& message) = 0; | |||
| /** Notification sent each time a packet of a multi-packet sysex message arrives. | |||
| If a long sysex message is broken up into multiple packets, this callback is made | |||
| for each packet that arrives until the message is finished, at which point | |||
| the normal handleIncomingMidiMessage() callback will be made with the entire | |||
| message. | |||
| The message passed in will contain the start of a sysex, but won't be finished | |||
| with the terminating 0xf7 byte. | |||
| */ | |||
| virtual void handlePartialSysexMessage (MidiInput* source, | |||
| const uint8* messageData, | |||
| int numBytesSoFar, | |||
| double timestamp) | |||
| { | |||
| // (this bit is just to avoid compiler warnings about unused variables) | |||
| (void) source; (void) messageData; (void) numBytesSoFar; (void) timestamp; | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| /** | |||
| Represents a midi input device. | |||
| To create one of these, use the static getDevices() method to find out what inputs are | |||
| available, and then use the openDevice() method to try to open one. | |||
| @see MidiOutput | |||
| */ | |||
| class JUCE_API MidiInput | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Returns a list of the available midi input devices. | |||
| You can open one of the devices by passing its index into the | |||
| openDevice() method. | |||
| @see getDefaultDeviceIndex, openDevice | |||
| */ | |||
| static StringArray getDevices(); | |||
| /** Returns the index of the default midi input device to use. | |||
| This refers to the index in the list returned by getDevices(). | |||
| */ | |||
| static int getDefaultDeviceIndex(); | |||
| /** Tries to open one of the midi input devices. | |||
| This will return a MidiInput object if it manages to open it. You can then | |||
| call start() and stop() on this device, and delete it when no longer needed. | |||
| If the device can't be opened, this will return a null pointer. | |||
| @param deviceIndex the index of a device from the list returned by getDevices() | |||
| @param callback the object that will receive the midi messages from this device. | |||
| @see MidiInputCallback, getDevices | |||
| */ | |||
| static MidiInput* openDevice (int deviceIndex, | |||
| MidiInputCallback* callback); | |||
| #if JUCE_LINUX || JUCE_MAC || JUCE_IOS || DOXYGEN | |||
| /** This will try to create a new midi input device (Not available on Windows). | |||
| This will attempt to create a new midi input device with the specified name, | |||
| for other apps to connect to. | |||
| Returns nullptr if a device can't be created. | |||
| @param deviceName the name to use for the new device | |||
| @param callback the object that will receive the midi messages from this device. | |||
| */ | |||
| static MidiInput* createNewDevice (const String& deviceName, | |||
| MidiInputCallback* callback); | |||
| #endif | |||
| //============================================================================== | |||
| /** Destructor. */ | |||
| virtual ~MidiInput(); | |||
| /** Returns the name of this device. */ | |||
| const String& getName() const noexcept { return name; } | |||
| /** Allows you to set a custom name for the device, in case you don't like the name | |||
| it was given when created. | |||
| */ | |||
| void setName (const String& newName) noexcept { name = newName; } | |||
| //============================================================================== | |||
| /** Starts the device running. | |||
| After calling this, the device will start sending midi messages to the | |||
| MidiInputCallback object that was specified when the openDevice() method | |||
| was called. | |||
| @see stop | |||
| */ | |||
| virtual void start(); | |||
| /** Stops the device running. | |||
| @see start | |||
| */ | |||
| virtual void stop(); | |||
| protected: | |||
| //============================================================================== | |||
| String name; | |||
| void* internal; | |||
| explicit MidiInput (const String& name); | |||
| private: | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInput) | |||
| }; | |||
| #endif // JUCE_MIDIINPUT_H_INCLUDED | |||
| @@ -1,153 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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. | |||
| ============================================================================== | |||
| */ | |||
| MidiMessageCollector::MidiMessageCollector() | |||
| : lastCallbackTime (0), | |||
| sampleRate (44100.0001) | |||
| { | |||
| } | |||
| MidiMessageCollector::~MidiMessageCollector() | |||
| { | |||
| } | |||
| //============================================================================== | |||
| void MidiMessageCollector::reset (const double sampleRate_) | |||
| { | |||
| jassert (sampleRate_ > 0); | |||
| const ScopedLock sl (midiCallbackLock); | |||
| sampleRate = sampleRate_; | |||
| incomingMessages.clear(); | |||
| lastCallbackTime = Time::getMillisecondCounterHiRes(); | |||
| } | |||
| void MidiMessageCollector::addMessageToQueue (const MidiMessage& message) | |||
| { | |||
| // you need to call reset() to set the correct sample rate before using this object | |||
| jassert (sampleRate != 44100.0001); | |||
| // the messages that come in here need to be time-stamped correctly - see MidiInput | |||
| // for details of what the number should be. | |||
| jassert (message.getTimeStamp() != 0); | |||
| const ScopedLock sl (midiCallbackLock); | |||
| const int sampleNumber | |||
| = (int) ((message.getTimeStamp() - 0.001 * lastCallbackTime) * sampleRate); | |||
| incomingMessages.addEvent (message, sampleNumber); | |||
| // if the messages don't get used for over a second, we'd better | |||
| // get rid of any old ones to avoid the queue getting too big | |||
| if (sampleNumber > sampleRate) | |||
| incomingMessages.clear (0, sampleNumber - (int) sampleRate); | |||
| } | |||
| void MidiMessageCollector::removeNextBlockOfMessages (MidiBuffer& destBuffer, | |||
| const int numSamples) | |||
| { | |||
| // you need to call reset() to set the correct sample rate before using this object | |||
| jassert (sampleRate != 44100.0001); | |||
| jassert (numSamples > 0); | |||
| const double timeNow = Time::getMillisecondCounterHiRes(); | |||
| const double msElapsed = timeNow - lastCallbackTime; | |||
| const ScopedLock sl (midiCallbackLock); | |||
| lastCallbackTime = timeNow; | |||
| if (! incomingMessages.isEmpty()) | |||
| { | |||
| int numSourceSamples = jmax (1, roundToInt (msElapsed * 0.001 * sampleRate)); | |||
| int startSample = 0; | |||
| int scale = 1 << 16; | |||
| const uint8* midiData; | |||
| int numBytes, samplePosition; | |||
| MidiBuffer::Iterator iter (incomingMessages); | |||
| if (numSourceSamples > numSamples) | |||
| { | |||
| // if our list of events is longer than the buffer we're being | |||
| // asked for, scale them down to squeeze them all in.. | |||
| const int maxBlockLengthToUse = numSamples << 5; | |||
| if (numSourceSamples > maxBlockLengthToUse) | |||
| { | |||
| startSample = numSourceSamples - maxBlockLengthToUse; | |||
| numSourceSamples = maxBlockLengthToUse; | |||
| iter.setNextSamplePosition (startSample); | |||
| } | |||
| scale = (numSamples << 10) / numSourceSamples; | |||
| while (iter.getNextEvent (midiData, numBytes, samplePosition)) | |||
| { | |||
| samplePosition = ((samplePosition - startSample) * scale) >> 10; | |||
| destBuffer.addEvent (midiData, numBytes, | |||
| jlimit (0, numSamples - 1, samplePosition)); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| // if our event list is shorter than the number we need, put them | |||
| // towards the end of the buffer | |||
| startSample = numSamples - numSourceSamples; | |||
| while (iter.getNextEvent (midiData, numBytes, samplePosition)) | |||
| { | |||
| destBuffer.addEvent (midiData, numBytes, | |||
| jlimit (0, numSamples - 1, samplePosition + startSample)); | |||
| } | |||
| } | |||
| incomingMessages.clear(); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| void MidiMessageCollector::handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) | |||
| { | |||
| MidiMessage m (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity)); | |||
| m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001); | |||
| addMessageToQueue (m); | |||
| } | |||
| void MidiMessageCollector::handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber) | |||
| { | |||
| MidiMessage m (MidiMessage::noteOff (midiChannel, midiNoteNumber)); | |||
| m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001); | |||
| addMessageToQueue (m); | |||
| } | |||
| void MidiMessageCollector::handleIncomingMidiMessage (MidiInput*, const MidiMessage& message) | |||
| { | |||
| addMessageToQueue (message); | |||
| } | |||
| @@ -1,104 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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_MIDIMESSAGECOLLECTOR_H_INCLUDED | |||
| #define JUCE_MIDIMESSAGECOLLECTOR_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| Collects incoming realtime MIDI messages and turns them into blocks suitable for | |||
| processing by a block-based audio callback. | |||
| The class can also be used as either a MidiKeyboardStateListener or a MidiInputCallback | |||
| so it can easily use a midi input or keyboard component as its source. | |||
| @see MidiMessage, MidiInput | |||
| */ | |||
| class JUCE_API MidiMessageCollector : public MidiKeyboardStateListener, | |||
| public MidiInputCallback | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates a MidiMessageCollector. */ | |||
| MidiMessageCollector(); | |||
| /** Destructor. */ | |||
| ~MidiMessageCollector(); | |||
| //============================================================================== | |||
| /** Clears any messages from the queue. | |||
| You need to call this method before starting to use the collector, so that | |||
| it knows the correct sample rate to use. | |||
| */ | |||
| void reset (double sampleRate); | |||
| /** Takes an incoming real-time message and adds it to the queue. | |||
| The message's timestamp is taken, and it will be ready for retrieval as part | |||
| of the block returned by the next call to removeNextBlockOfMessages(). | |||
| This method is fully thread-safe when overlapping calls are made with | |||
| removeNextBlockOfMessages(). | |||
| */ | |||
| void addMessageToQueue (const MidiMessage& message); | |||
| /** Removes all the pending messages from the queue as a buffer. | |||
| This will also correct the messages' timestamps to make sure they're in | |||
| the range 0 to numSamples - 1. | |||
| This call should be made regularly by something like an audio processing | |||
| callback, because the time that it happens is used in calculating the | |||
| midi event positions. | |||
| This method is fully thread-safe when overlapping calls are made with | |||
| addMessageToQueue(). | |||
| Precondition: numSamples must be greater than 0. | |||
| */ | |||
| void removeNextBlockOfMessages (MidiBuffer& destBuffer, int numSamples); | |||
| //============================================================================== | |||
| /** @internal */ | |||
| void handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override; | |||
| /** @internal */ | |||
| void handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber) override; | |||
| /** @internal */ | |||
| void handleIncomingMidiMessage (MidiInput*, const MidiMessage&) override; | |||
| private: | |||
| //============================================================================== | |||
| double lastCallbackTime; | |||
| CriticalSection midiCallbackLock; | |||
| MidiBuffer incomingMessages; | |||
| double sampleRate; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiMessageCollector) | |||
| }; | |||
| #endif // JUCE_MIDIMESSAGECOLLECTOR_H_INCLUDED | |||
| @@ -1,162 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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. | |||
| ============================================================================== | |||
| */ | |||
| struct MidiOutput::PendingMessage | |||
| { | |||
| PendingMessage (const void* const data, const int len, const double timeStamp) | |||
| : message (data, len, timeStamp) | |||
| {} | |||
| MidiMessage message; | |||
| PendingMessage* next; | |||
| }; | |||
| MidiOutput::MidiOutput() | |||
| : Thread ("midi out"), | |||
| internal (nullptr), | |||
| firstMessage (nullptr) | |||
| { | |||
| } | |||
| void MidiOutput::sendBlockOfMessages (const MidiBuffer& buffer, | |||
| const double millisecondCounterToStartAt, | |||
| double samplesPerSecondForBuffer) | |||
| { | |||
| // You've got to call startBackgroundThread() for this to actually work.. | |||
| jassert (isThreadRunning()); | |||
| // this needs to be a value in the future - RTFM for this method! | |||
| jassert (millisecondCounterToStartAt > 0); | |||
| const double timeScaleFactor = 1000.0 / samplesPerSecondForBuffer; | |||
| MidiBuffer::Iterator i (buffer); | |||
| const uint8* data; | |||
| int len, time; | |||
| while (i.getNextEvent (data, len, time)) | |||
| { | |||
| const double eventTime = millisecondCounterToStartAt + timeScaleFactor * time; | |||
| PendingMessage* const m = new PendingMessage (data, len, eventTime); | |||
| const ScopedLock sl (lock); | |||
| if (firstMessage == nullptr || firstMessage->message.getTimeStamp() > eventTime) | |||
| { | |||
| m->next = firstMessage; | |||
| firstMessage = m; | |||
| } | |||
| else | |||
| { | |||
| PendingMessage* mm = firstMessage; | |||
| while (mm->next != nullptr && mm->next->message.getTimeStamp() <= eventTime) | |||
| mm = mm->next; | |||
| m->next = mm->next; | |||
| mm->next = m; | |||
| } | |||
| } | |||
| notify(); | |||
| } | |||
| void MidiOutput::clearAllPendingMessages() | |||
| { | |||
| const ScopedLock sl (lock); | |||
| while (firstMessage != nullptr) | |||
| { | |||
| PendingMessage* const m = firstMessage; | |||
| firstMessage = firstMessage->next; | |||
| delete m; | |||
| } | |||
| } | |||
| void MidiOutput::startBackgroundThread() | |||
| { | |||
| startThread (9); | |||
| } | |||
| void MidiOutput::stopBackgroundThread() | |||
| { | |||
| stopThread (5000); | |||
| } | |||
| void MidiOutput::run() | |||
| { | |||
| while (! threadShouldExit()) | |||
| { | |||
| uint32 now = Time::getMillisecondCounter(); | |||
| uint32 eventTime = 0; | |||
| uint32 timeToWait = 500; | |||
| PendingMessage* message; | |||
| { | |||
| const ScopedLock sl (lock); | |||
| message = firstMessage; | |||
| if (message != nullptr) | |||
| { | |||
| eventTime = (uint32) roundToInt (message->message.getTimeStamp()); | |||
| if (eventTime > now + 20) | |||
| { | |||
| timeToWait = eventTime - (now + 20); | |||
| message = nullptr; | |||
| } | |||
| else | |||
| { | |||
| firstMessage = message->next; | |||
| } | |||
| } | |||
| } | |||
| if (message != nullptr) | |||
| { | |||
| const ScopedPointer<PendingMessage> messageDeleter (message); | |||
| if (eventTime > now) | |||
| { | |||
| Time::waitForMillisecondCounter (eventTime); | |||
| if (threadShouldExit()) | |||
| break; | |||
| } | |||
| if (eventTime > now - 200) | |||
| sendMessageNow (message->message); | |||
| } | |||
| else | |||
| { | |||
| jassert (timeToWait < 1000 * 30); | |||
| wait ((int) timeToWait); | |||
| } | |||
| } | |||
| clearAllPendingMessages(); | |||
| } | |||
| @@ -1,147 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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_MIDIOUTPUT_H_INCLUDED | |||
| #define JUCE_MIDIOUTPUT_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| Controls a physical MIDI output device. | |||
| To create one of these, use the static getDevices() method to get a list of the | |||
| available output devices, then use the openDevice() method to try to open one. | |||
| @see MidiInput | |||
| */ | |||
| class JUCE_API MidiOutput : private Thread | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Returns a list of the available midi output devices. | |||
| You can open one of the devices by passing its index into the | |||
| openDevice() method. | |||
| @see getDefaultDeviceIndex, openDevice | |||
| */ | |||
| static StringArray getDevices(); | |||
| /** Returns the index of the default midi output device to use. | |||
| This refers to the index in the list returned by getDevices(). | |||
| */ | |||
| static int getDefaultDeviceIndex(); | |||
| /** Tries to open one of the midi output devices. | |||
| This will return a MidiOutput object if it manages to open it. You can then | |||
| send messages to this device, and delete it when no longer needed. | |||
| If the device can't be opened, this will return a null pointer. | |||
| @param deviceIndex the index of a device from the list returned by getDevices() | |||
| @see getDevices | |||
| */ | |||
| static MidiOutput* openDevice (int deviceIndex); | |||
| #if JUCE_LINUX || JUCE_MAC || JUCE_IOS || DOXYGEN | |||
| /** This will try to create a new midi output device (Not available on Windows). | |||
| This will attempt to create a new midi output device that other apps can connect | |||
| to and use as their midi input. | |||
| Returns nullptr if a device can't be created. | |||
| @param deviceName the name to use for the new device | |||
| */ | |||
| static MidiOutput* createNewDevice (const String& deviceName); | |||
| #endif | |||
| //============================================================================== | |||
| /** Destructor. */ | |||
| virtual ~MidiOutput(); | |||
| /** Makes this device output a midi message. | |||
| @see MidiMessage | |||
| */ | |||
| virtual void sendMessageNow (const MidiMessage& message); | |||
| //============================================================================== | |||
| /** This lets you supply a block of messages that will be sent out at some point | |||
| in the future. | |||
| The MidiOutput class has an internal thread that can send out timestamped | |||
| messages - this appends a set of messages to its internal buffer, ready for | |||
| sending. | |||
| This will only work if you've already started the thread with startBackgroundThread(). | |||
| A time is supplied, at which the block of messages should be sent. This time uses | |||
| the same time base as Time::getMillisecondCounter(), and must be in the future. | |||
| The samplesPerSecondForBuffer parameter indicates the number of samples per second | |||
| used by the MidiBuffer. Each event in a MidiBuffer has a sample position, and the | |||
| samplesPerSecondForBuffer value is needed to convert this sample position to a | |||
| real time. | |||
| */ | |||
| virtual void sendBlockOfMessages (const MidiBuffer& buffer, | |||
| double millisecondCounterToStartAt, | |||
| double samplesPerSecondForBuffer); | |||
| /** Gets rid of any midi messages that had been added by sendBlockOfMessages(). | |||
| */ | |||
| virtual void clearAllPendingMessages(); | |||
| /** Starts up a background thread so that the device can send blocks of data. | |||
| Call this to get the device ready, before using sendBlockOfMessages(). | |||
| */ | |||
| virtual void startBackgroundThread(); | |||
| /** Stops the background thread, and clears any pending midi events. | |||
| @see startBackgroundThread | |||
| */ | |||
| virtual void stopBackgroundThread(); | |||
| protected: | |||
| //============================================================================== | |||
| void* internal; | |||
| CriticalSection lock; | |||
| struct PendingMessage; | |||
| PendingMessage* firstMessage; | |||
| MidiOutput(); | |||
| void run() override; | |||
| private: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutput) | |||
| }; | |||
| #endif // JUCE_MIDIOUTPUT_H_INCLUDED | |||
| @@ -1,187 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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_MIDIDATACONCATENATOR_H_INCLUDED | |||
| #define JUCE_MIDIDATACONCATENATOR_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| Helper class that takes chunks of incoming midi bytes, packages them into | |||
| messages, and dispatches them to a midi callback. | |||
| */ | |||
| class MidiDataConcatenator | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| MidiDataConcatenator (const int initialBufferSize) | |||
| : pendingData ((size_t) initialBufferSize), | |||
| pendingDataTime (0), pendingBytes (0), runningStatus (0) | |||
| { | |||
| } | |||
| void reset() | |||
| { | |||
| pendingBytes = 0; | |||
| runningStatus = 0; | |||
| pendingDataTime = 0; | |||
| } | |||
| template <typename UserDataType, typename CallbackType> | |||
| void pushMidiData (const void* inputData, int numBytes, double time, | |||
| UserDataType* input, CallbackType& callback) | |||
| { | |||
| const uint8* d = static_cast <const uint8*> (inputData); | |||
| while (numBytes > 0) | |||
| { | |||
| if (pendingBytes > 0 || d[0] == 0xf0) | |||
| { | |||
| processSysex (d, numBytes, time, input, callback); | |||
| runningStatus = 0; | |||
| } | |||
| else | |||
| { | |||
| int len = 0; | |||
| uint8 data[3]; | |||
| while (numBytes > 0) | |||
| { | |||
| // If there's a realtime message embedded in the middle of | |||
| // the normal message, handle it now.. | |||
| if (*d >= 0xf8 && *d <= 0xfe) | |||
| { | |||
| const MidiMessage m (*d++, time); | |||
| callback.handleIncomingMidiMessage (input, m); | |||
| --numBytes; | |||
| } | |||
| else | |||
| { | |||
| if (len == 0 && *d < 0x80 && runningStatus >= 0x80) | |||
| data[len++] = runningStatus; | |||
| data[len++] = *d++; | |||
| --numBytes; | |||
| if (len >= MidiMessage::getMessageLengthFromFirstByte (data[0])) | |||
| break; | |||
| } | |||
| } | |||
| if (len > 0) | |||
| { | |||
| int used = 0; | |||
| const MidiMessage m (data, len, used, 0, time); | |||
| if (used <= 0) | |||
| break; // malformed message.. | |||
| jassert (used == len); | |||
| callback.handleIncomingMidiMessage (input, m); | |||
| runningStatus = data[0]; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| private: | |||
| template <typename UserDataType, typename CallbackType> | |||
| void processSysex (const uint8*& d, int& numBytes, double time, | |||
| UserDataType* input, CallbackType& callback) | |||
| { | |||
| if (*d == 0xf0) | |||
| { | |||
| pendingBytes = 0; | |||
| pendingDataTime = time; | |||
| } | |||
| pendingData.ensureSize ((size_t) (pendingBytes + numBytes), false); | |||
| uint8* totalMessage = static_cast<uint8*> (pendingData.getData()); | |||
| uint8* dest = totalMessage + pendingBytes; | |||
| do | |||
| { | |||
| if (pendingBytes > 0 && *d >= 0x80) | |||
| { | |||
| if (*d == 0xf7) | |||
| { | |||
| *dest++ = *d++; | |||
| ++pendingBytes; | |||
| --numBytes; | |||
| break; | |||
| } | |||
| if (*d >= 0xfa || *d == 0xf8) | |||
| { | |||
| callback.handleIncomingMidiMessage (input, MidiMessage (*d, time)); | |||
| ++d; | |||
| --numBytes; | |||
| } | |||
| else | |||
| { | |||
| pendingBytes = 0; | |||
| int used = 0; | |||
| const MidiMessage m (d, numBytes, used, 0, time); | |||
| if (used > 0) | |||
| { | |||
| callback.handleIncomingMidiMessage (input, m); | |||
| numBytes -= used; | |||
| d += used; | |||
| } | |||
| break; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| *dest++ = *d++; | |||
| ++pendingBytes; | |||
| --numBytes; | |||
| } | |||
| } | |||
| while (numBytes > 0); | |||
| if (pendingBytes > 0) | |||
| { | |||
| if (totalMessage [pendingBytes - 1] == 0xf7) | |||
| { | |||
| callback.handleIncomingMidiMessage (input, MidiMessage (totalMessage, pendingBytes, pendingDataTime)); | |||
| pendingBytes = 0; | |||
| } | |||
| else | |||
| { | |||
| callback.handlePartialSysexMessage (input, totalMessage, pendingBytes, pendingDataTime); | |||
| } | |||
| } | |||
| } | |||
| MemoryBlock pendingData; | |||
| double pendingDataTime; | |||
| int pendingBytes; | |||
| uint8 runningStatus; | |||
| JUCE_DECLARE_NON_COPYABLE (MidiDataConcatenator) | |||
| }; | |||
| #endif // JUCE_MIDIDATACONCATENATOR_H_INCLUDED | |||
| @@ -1,450 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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. | |||
| ============================================================================== | |||
| */ | |||
| //============================================================================== | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | |||
| STATICMETHOD (getMinBufferSize, "getMinBufferSize", "(III)I") \ | |||
| STATICMETHOD (getNativeOutputSampleRate, "getNativeOutputSampleRate", "(I)I") \ | |||
| METHOD (constructor, "<init>", "(IIIIII)V") \ | |||
| METHOD (getState, "getState", "()I") \ | |||
| METHOD (play, "play", "()V") \ | |||
| METHOD (stop, "stop", "()V") \ | |||
| METHOD (release, "release", "()V") \ | |||
| METHOD (flush, "flush", "()V") \ | |||
| METHOD (write, "write", "([SII)I") \ | |||
| DECLARE_JNI_CLASS (AudioTrack, "android/media/AudioTrack"); | |||
| #undef JNI_CLASS_MEMBERS | |||
| //============================================================================== | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | |||
| STATICMETHOD (getMinBufferSize, "getMinBufferSize", "(III)I") \ | |||
| METHOD (constructor, "<init>", "(IIIII)V") \ | |||
| METHOD (getState, "getState", "()I") \ | |||
| METHOD (startRecording, "startRecording", "()V") \ | |||
| METHOD (stop, "stop", "()V") \ | |||
| METHOD (read, "read", "([SII)I") \ | |||
| METHOD (release, "release", "()V") \ | |||
| DECLARE_JNI_CLASS (AudioRecord, "android/media/AudioRecord"); | |||
| #undef JNI_CLASS_MEMBERS | |||
| //============================================================================== | |||
| enum | |||
| { | |||
| CHANNEL_OUT_STEREO = 12, | |||
| CHANNEL_IN_STEREO = 12, | |||
| CHANNEL_IN_MONO = 16, | |||
| ENCODING_PCM_16BIT = 2, | |||
| STREAM_MUSIC = 3, | |||
| MODE_STREAM = 1, | |||
| STATE_UNINITIALIZED = 0 | |||
| }; | |||
| const char* const javaAudioTypeName = "Android Audio"; | |||
| //============================================================================== | |||
| class AndroidAudioIODevice : public AudioIODevice, | |||
| public Thread | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| AndroidAudioIODevice (const String& deviceName) | |||
| : AudioIODevice (deviceName, javaAudioTypeName), | |||
| Thread ("audio"), | |||
| minBufferSizeOut (0), minBufferSizeIn (0), callback (0), sampleRate (0), | |||
| numClientInputChannels (0), numDeviceInputChannels (0), numDeviceInputChannelsAvailable (2), | |||
| numClientOutputChannels (0), numDeviceOutputChannels (0), | |||
| actualBufferSize (0), isRunning (false), | |||
| inputChannelBuffer (1, 1), | |||
| outputChannelBuffer (1, 1) | |||
| { | |||
| JNIEnv* env = getEnv(); | |||
| sampleRate = env->CallStaticIntMethod (AudioTrack, AudioTrack.getNativeOutputSampleRate, MODE_STREAM); | |||
| minBufferSizeOut = (int) env->CallStaticIntMethod (AudioTrack, AudioTrack.getMinBufferSize, sampleRate, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT); | |||
| minBufferSizeIn = (int) env->CallStaticIntMethod (AudioRecord, AudioRecord.getMinBufferSize, sampleRate, CHANNEL_IN_STEREO, ENCODING_PCM_16BIT); | |||
| if (minBufferSizeIn <= 0) | |||
| { | |||
| minBufferSizeIn = env->CallStaticIntMethod (AudioRecord, AudioRecord.getMinBufferSize, sampleRate, CHANNEL_IN_MONO, ENCODING_PCM_16BIT); | |||
| if (minBufferSizeIn > 0) | |||
| numDeviceInputChannelsAvailable = 1; | |||
| else | |||
| numDeviceInputChannelsAvailable = 0; | |||
| } | |||
| DBG ("Audio device - min buffers: " << minBufferSizeOut << ", " << minBufferSizeIn << "; " | |||
| << sampleRate << " Hz; input chans: " << numDeviceInputChannelsAvailable); | |||
| } | |||
| ~AndroidAudioIODevice() | |||
| { | |||
| close(); | |||
| } | |||
| StringArray getOutputChannelNames() override | |||
| { | |||
| StringArray s; | |||
| s.add ("Left"); | |||
| s.add ("Right"); | |||
| return s; | |||
| } | |||
| StringArray getInputChannelNames() override | |||
| { | |||
| StringArray s; | |||
| if (numDeviceInputChannelsAvailable == 2) | |||
| { | |||
| s.add ("Left"); | |||
| s.add ("Right"); | |||
| } | |||
| else if (numDeviceInputChannelsAvailable == 1) | |||
| { | |||
| s.add ("Audio Input"); | |||
| } | |||
| return s; | |||
| } | |||
| Array<double> getAvailableSampleRates() override | |||
| { | |||
| Array<double> r; | |||
| r.add ((double) sampleRate); | |||
| return r; | |||
| } | |||
| Array<int> getAvailableBufferSizes() override | |||
| { | |||
| Array<int> b; | |||
| int n = 16; | |||
| for (int i = 0; i < 50; ++i) | |||
| { | |||
| b.add (n); | |||
| n += n < 64 ? 16 | |||
| : (n < 512 ? 32 | |||
| : (n < 1024 ? 64 | |||
| : (n < 2048 ? 128 : 256))); | |||
| } | |||
| return b; | |||
| } | |||
| int getDefaultBufferSize() override { return 2048; } | |||
| String open (const BigInteger& inputChannels, | |||
| const BigInteger& outputChannels, | |||
| double requestedSampleRate, | |||
| int bufferSize) override | |||
| { | |||
| close(); | |||
| if (sampleRate != (int) requestedSampleRate) | |||
| return "Sample rate not allowed"; | |||
| lastError.clear(); | |||
| int preferredBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize; | |||
| numDeviceInputChannels = 0; | |||
| numDeviceOutputChannels = 0; | |||
| activeOutputChans = outputChannels; | |||
| activeOutputChans.setRange (2, activeOutputChans.getHighestBit(), false); | |||
| numClientOutputChannels = activeOutputChans.countNumberOfSetBits(); | |||
| activeInputChans = inputChannels; | |||
| activeInputChans.setRange (2, activeInputChans.getHighestBit(), false); | |||
| numClientInputChannels = activeInputChans.countNumberOfSetBits(); | |||
| actualBufferSize = preferredBufferSize; | |||
| inputChannelBuffer.setSize (2, actualBufferSize); | |||
| inputChannelBuffer.clear(); | |||
| outputChannelBuffer.setSize (2, actualBufferSize); | |||
| outputChannelBuffer.clear(); | |||
| JNIEnv* env = getEnv(); | |||
| if (numClientOutputChannels > 0) | |||
| { | |||
| numDeviceOutputChannels = 2; | |||
| outputDevice = GlobalRef (env->NewObject (AudioTrack, AudioTrack.constructor, | |||
| STREAM_MUSIC, sampleRate, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT, | |||
| (jint) (minBufferSizeOut * numDeviceOutputChannels * sizeof (int16)), MODE_STREAM)); | |||
| if (env->CallIntMethod (outputDevice, AudioTrack.getState) != STATE_UNINITIALIZED) | |||
| isRunning = true; | |||
| else | |||
| outputDevice.clear(); // failed to open the device | |||
| } | |||
| if (numClientInputChannels > 0 && numDeviceInputChannelsAvailable > 0) | |||
| { | |||
| numDeviceInputChannels = jmin (numClientInputChannels, numDeviceInputChannelsAvailable); | |||
| inputDevice = GlobalRef (env->NewObject (AudioRecord, AudioRecord.constructor, | |||
| 0 /* (default audio source) */, sampleRate, | |||
| numDeviceInputChannelsAvailable > 1 ? CHANNEL_IN_STEREO : CHANNEL_IN_MONO, | |||
| ENCODING_PCM_16BIT, | |||
| (jint) (minBufferSizeIn * numDeviceInputChannels * sizeof (int16)))); | |||
| if (env->CallIntMethod (inputDevice, AudioRecord.getState) != STATE_UNINITIALIZED) | |||
| isRunning = true; | |||
| else | |||
| inputDevice.clear(); // failed to open the device | |||
| } | |||
| if (isRunning) | |||
| { | |||
| if (outputDevice != nullptr) | |||
| env->CallVoidMethod (outputDevice, AudioTrack.play); | |||
| if (inputDevice != nullptr) | |||
| env->CallVoidMethod (inputDevice, AudioRecord.startRecording); | |||
| startThread (8); | |||
| } | |||
| else | |||
| { | |||
| closeDevices(); | |||
| } | |||
| return lastError; | |||
| } | |||
| void close() override | |||
| { | |||
| if (isRunning) | |||
| { | |||
| stopThread (2000); | |||
| isRunning = false; | |||
| closeDevices(); | |||
| } | |||
| } | |||
| int getOutputLatencyInSamples() override { return (minBufferSizeOut * 3) / 4; } | |||
| int getInputLatencyInSamples() override { return (minBufferSizeIn * 3) / 4; } | |||
| bool isOpen() override { return isRunning; } | |||
| int getCurrentBufferSizeSamples() override { return actualBufferSize; } | |||
| int getCurrentBitDepth() override { return 16; } | |||
| double getCurrentSampleRate() override { return sampleRate; } | |||
| BigInteger getActiveOutputChannels() const override { return activeOutputChans; } | |||
| BigInteger getActiveInputChannels() const override { return activeInputChans; } | |||
| String getLastError() override { return lastError; } | |||
| bool isPlaying() override { return isRunning && callback != 0; } | |||
| void start (AudioIODeviceCallback* newCallback) override | |||
| { | |||
| if (isRunning && callback != newCallback) | |||
| { | |||
| if (newCallback != nullptr) | |||
| newCallback->audioDeviceAboutToStart (this); | |||
| const ScopedLock sl (callbackLock); | |||
| callback = newCallback; | |||
| } | |||
| } | |||
| void stop() override | |||
| { | |||
| if (isRunning) | |||
| { | |||
| AudioIODeviceCallback* lastCallback; | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| lastCallback = callback; | |||
| callback = nullptr; | |||
| } | |||
| if (lastCallback != nullptr) | |||
| lastCallback->audioDeviceStopped(); | |||
| } | |||
| } | |||
| void run() override | |||
| { | |||
| JNIEnv* env = getEnv(); | |||
| jshortArray audioBuffer = env->NewShortArray (actualBufferSize * jmax (numDeviceOutputChannels, numDeviceInputChannels)); | |||
| while (! threadShouldExit()) | |||
| { | |||
| if (inputDevice != nullptr) | |||
| { | |||
| jint numRead = env->CallIntMethod (inputDevice, AudioRecord.read, audioBuffer, 0, actualBufferSize * numDeviceInputChannels); | |||
| if (numRead < actualBufferSize * numDeviceInputChannels) | |||
| { | |||
| DBG ("Audio read under-run! " << numRead); | |||
| } | |||
| jshort* const src = env->GetShortArrayElements (audioBuffer, 0); | |||
| for (int chan = 0; chan < inputChannelBuffer.getNumChannels(); ++chan) | |||
| { | |||
| AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst> d (inputChannelBuffer.getWritePointer (chan)); | |||
| if (chan < numDeviceInputChannels) | |||
| { | |||
| AudioData::Pointer <AudioData::Int16, AudioData::NativeEndian, AudioData::Interleaved, AudioData::Const> s (src + chan, numDeviceInputChannels); | |||
| d.convertSamples (s, actualBufferSize); | |||
| } | |||
| else | |||
| { | |||
| d.clearSamples (actualBufferSize); | |||
| } | |||
| } | |||
| env->ReleaseShortArrayElements (audioBuffer, src, 0); | |||
| } | |||
| if (threadShouldExit()) | |||
| break; | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| if (callback != nullptr) | |||
| { | |||
| callback->audioDeviceIOCallback (inputChannelBuffer.getArrayOfReadPointers(), numClientInputChannels, | |||
| outputChannelBuffer.getArrayOfWritePointers(), numClientOutputChannels, | |||
| actualBufferSize); | |||
| } | |||
| else | |||
| { | |||
| outputChannelBuffer.clear(); | |||
| } | |||
| } | |||
| if (outputDevice != nullptr) | |||
| { | |||
| if (threadShouldExit()) | |||
| break; | |||
| jshort* const dest = env->GetShortArrayElements (audioBuffer, 0); | |||
| for (int chan = 0; chan < numDeviceOutputChannels; ++chan) | |||
| { | |||
| AudioData::Pointer <AudioData::Int16, AudioData::NativeEndian, AudioData::Interleaved, AudioData::NonConst> d (dest + chan, numDeviceOutputChannels); | |||
| const float* const sourceChanData = outputChannelBuffer.getReadPointer (jmin (chan, outputChannelBuffer.getNumChannels() - 1)); | |||
| AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const> s (sourceChanData); | |||
| d.convertSamples (s, actualBufferSize); | |||
| } | |||
| env->ReleaseShortArrayElements (audioBuffer, dest, 0); | |||
| jint numWritten = env->CallIntMethod (outputDevice, AudioTrack.write, audioBuffer, 0, actualBufferSize * numDeviceOutputChannels); | |||
| if (numWritten < actualBufferSize * numDeviceOutputChannels) | |||
| { | |||
| DBG ("Audio write underrun! " << numWritten); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| int minBufferSizeOut, minBufferSizeIn; | |||
| private: | |||
| //================================================================================================== | |||
| CriticalSection callbackLock; | |||
| AudioIODeviceCallback* callback; | |||
| jint sampleRate; | |||
| int numClientInputChannels, numDeviceInputChannels, numDeviceInputChannelsAvailable; | |||
| int numClientOutputChannels, numDeviceOutputChannels; | |||
| int actualBufferSize; | |||
| bool isRunning; | |||
| String lastError; | |||
| BigInteger activeOutputChans, activeInputChans; | |||
| GlobalRef outputDevice, inputDevice; | |||
| AudioSampleBuffer inputChannelBuffer, outputChannelBuffer; | |||
| void closeDevices() | |||
| { | |||
| if (outputDevice != nullptr) | |||
| { | |||
| outputDevice.callVoidMethod (AudioTrack.stop); | |||
| outputDevice.callVoidMethod (AudioTrack.release); | |||
| outputDevice.clear(); | |||
| } | |||
| if (inputDevice != nullptr) | |||
| { | |||
| inputDevice.callVoidMethod (AudioRecord.stop); | |||
| inputDevice.callVoidMethod (AudioRecord.release); | |||
| inputDevice.clear(); | |||
| } | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE (AndroidAudioIODevice) | |||
| }; | |||
| //============================================================================== | |||
| class AndroidAudioIODeviceType : public AudioIODeviceType | |||
| { | |||
| public: | |||
| AndroidAudioIODeviceType() : AudioIODeviceType (javaAudioTypeName) {} | |||
| //============================================================================== | |||
| void scanForDevices() {} | |||
| StringArray getDeviceNames (bool wantInputNames) const { return StringArray (javaAudioTypeName); } | |||
| int getDefaultDeviceIndex (bool forInput) const { return 0; } | |||
| int getIndexOfDevice (AudioIODevice* device, bool asInput) const { return device != nullptr ? 0 : -1; } | |||
| bool hasSeparateInputsAndOutputs() const { return false; } | |||
| AudioIODevice* createDevice (const String& outputDeviceName, | |||
| const String& inputDeviceName) | |||
| { | |||
| ScopedPointer<AndroidAudioIODevice> dev; | |||
| if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty()) | |||
| { | |||
| dev = new AndroidAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName | |||
| : inputDeviceName); | |||
| if (dev->getCurrentSampleRate() <= 0 || dev->getDefaultBufferSize() <= 0) | |||
| dev = nullptr; | |||
| } | |||
| return dev.release(); | |||
| } | |||
| private: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AndroidAudioIODeviceType) | |||
| }; | |||
| //============================================================================== | |||
| extern bool isOpenSLAvailable(); | |||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android() | |||
| { | |||
| #if JUCE_USE_ANDROID_OPENSLES | |||
| if (isOpenSLAvailable()) | |||
| return nullptr; | |||
| #endif | |||
| return new AndroidAudioIODeviceType(); | |||
| } | |||
| @@ -1,85 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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. | |||
| ============================================================================== | |||
| */ | |||
| StringArray MidiOutput::getDevices() | |||
| { | |||
| StringArray devices; | |||
| return devices; | |||
| } | |||
| int MidiOutput::getDefaultDeviceIndex() | |||
| { | |||
| return 0; | |||
| } | |||
| MidiOutput* MidiOutput::openDevice (int index) | |||
| { | |||
| return nullptr; | |||
| } | |||
| MidiOutput::~MidiOutput() | |||
| { | |||
| stopBackgroundThread(); | |||
| } | |||
| void MidiOutput::sendMessageNow (const MidiMessage&) | |||
| { | |||
| } | |||
| //============================================================================== | |||
| MidiInput::MidiInput (const String& name_) | |||
| : name (name_), | |||
| internal (0) | |||
| { | |||
| } | |||
| MidiInput::~MidiInput() | |||
| { | |||
| } | |||
| void MidiInput::start() | |||
| { | |||
| } | |||
| void MidiInput::stop() | |||
| { | |||
| } | |||
| int MidiInput::getDefaultDeviceIndex() | |||
| { | |||
| return 0; | |||
| } | |||
| StringArray MidiInput::getDevices() | |||
| { | |||
| StringArray devs; | |||
| return devs; | |||
| } | |||
| MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) | |||
| { | |||
| return nullptr; | |||
| } | |||
| @@ -1,632 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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. | |||
| ============================================================================== | |||
| */ | |||
| const char* const openSLTypeName = "Android OpenSL"; | |||
| bool isOpenSLAvailable() | |||
| { | |||
| DynamicLibrary library; | |||
| return library.open ("libOpenSLES.so"); | |||
| } | |||
| //============================================================================== | |||
| class OpenSLAudioIODevice : public AudioIODevice, | |||
| public Thread | |||
| { | |||
| public: | |||
| OpenSLAudioIODevice (const String& deviceName) | |||
| : AudioIODevice (deviceName, openSLTypeName), | |||
| Thread ("OpenSL"), | |||
| callback (nullptr), sampleRate (0), deviceOpen (false), | |||
| inputBuffer (2, 2), outputBuffer (2, 2) | |||
| { | |||
| // OpenSL has piss-poor support for determining latency, so the only way I can find to | |||
| // get a number for this is by asking the AudioTrack/AudioRecord classes.. | |||
| AndroidAudioIODevice javaDevice (String::empty); | |||
| // this is a total guess about how to calculate the latency, but seems to vaguely agree | |||
| // with the devices I've tested.. YMMV | |||
| inputLatency = ((javaDevice.minBufferSizeIn * 2) / 3); | |||
| outputLatency = ((javaDevice.minBufferSizeOut * 2) / 3); | |||
| const int longestLatency = jmax (inputLatency, outputLatency); | |||
| const int totalLatency = inputLatency + outputLatency; | |||
| inputLatency = ((longestLatency * inputLatency) / totalLatency) & ~15; | |||
| outputLatency = ((longestLatency * outputLatency) / totalLatency) & ~15; | |||
| } | |||
| ~OpenSLAudioIODevice() | |||
| { | |||
| close(); | |||
| } | |||
| bool openedOk() const { return engine.outputMixObject != nullptr; } | |||
| StringArray getOutputChannelNames() override | |||
| { | |||
| StringArray s; | |||
| s.add ("Left"); | |||
| s.add ("Right"); | |||
| return s; | |||
| } | |||
| StringArray getInputChannelNames() override | |||
| { | |||
| StringArray s; | |||
| s.add ("Audio Input"); | |||
| return s; | |||
| } | |||
| Array<double> getAvailableSampleRates() override | |||
| { | |||
| static const double rates[] = { 8000.0, 16000.0, 32000.0, 44100.0, 48000.0 }; | |||
| return Array<double> (rates, numElementsInArray (rates)); | |||
| } | |||
| Array<int> getAvailableBufferSizes() override | |||
| { | |||
| static const int sizes[] = { 256, 512, 768, 1024, 1280, 1600 }; // must all be multiples of the block size | |||
| return Array<int> (sizes, numElementsInArray (sizes)); | |||
| } | |||
| String open (const BigInteger& inputChannels, | |||
| const BigInteger& outputChannels, | |||
| double requestedSampleRate, | |||
| int bufferSize) override | |||
| { | |||
| close(); | |||
| lastError.clear(); | |||
| sampleRate = (int) requestedSampleRate; | |||
| int preferredBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize; | |||
| activeOutputChans = outputChannels; | |||
| activeOutputChans.setRange (2, activeOutputChans.getHighestBit(), false); | |||
| numOutputChannels = activeOutputChans.countNumberOfSetBits(); | |||
| activeInputChans = inputChannels; | |||
| activeInputChans.setRange (1, activeInputChans.getHighestBit(), false); | |||
| numInputChannels = activeInputChans.countNumberOfSetBits(); | |||
| actualBufferSize = preferredBufferSize; | |||
| inputBuffer.setSize (jmax (1, numInputChannels), actualBufferSize); | |||
| outputBuffer.setSize (jmax (1, numOutputChannels), actualBufferSize); | |||
| outputBuffer.clear(); | |||
| recorder = engine.createRecorder (numInputChannels, sampleRate); | |||
| player = engine.createPlayer (numOutputChannels, sampleRate); | |||
| startThread (8); | |||
| deviceOpen = true; | |||
| return lastError; | |||
| } | |||
| void close() override | |||
| { | |||
| stop(); | |||
| stopThread (6000); | |||
| deviceOpen = false; | |||
| recorder = nullptr; | |||
| player = nullptr; | |||
| } | |||
| int getDefaultBufferSize() override { return 1024; } | |||
| int getOutputLatencyInSamples() override { return outputLatency; } | |||
| int getInputLatencyInSamples() override { return inputLatency; } | |||
| bool isOpen() override { return deviceOpen; } | |||
| int getCurrentBufferSizeSamples() override { return actualBufferSize; } | |||
| int getCurrentBitDepth() override { return 16; } | |||
| double getCurrentSampleRate() override { return sampleRate; } | |||
| BigInteger getActiveOutputChannels() const override { return activeOutputChans; } | |||
| BigInteger getActiveInputChannels() const override { return activeInputChans; } | |||
| String getLastError() override { return lastError; } | |||
| bool isPlaying() override { return callback != nullptr; } | |||
| void start (AudioIODeviceCallback* newCallback) override | |||
| { | |||
| stop(); | |||
| if (deviceOpen && callback != newCallback) | |||
| { | |||
| if (newCallback != nullptr) | |||
| newCallback->audioDeviceAboutToStart (this); | |||
| setCallback (newCallback); | |||
| } | |||
| } | |||
| void stop() override | |||
| { | |||
| if (AudioIODeviceCallback* const oldCallback = setCallback (nullptr)) | |||
| oldCallback->audioDeviceStopped(); | |||
| } | |||
| bool setAudioPreprocessingEnabled (bool enable) override | |||
| { | |||
| return recorder != nullptr && recorder->setAudioPreprocessingEnabled (enable); | |||
| } | |||
| private: | |||
| //================================================================================================== | |||
| CriticalSection callbackLock; | |||
| AudioIODeviceCallback* callback; | |||
| int actualBufferSize, sampleRate; | |||
| int inputLatency, outputLatency; | |||
| bool deviceOpen; | |||
| String lastError; | |||
| BigInteger activeOutputChans, activeInputChans; | |||
| int numInputChannels, numOutputChannels; | |||
| AudioSampleBuffer inputBuffer, outputBuffer; | |||
| struct Player; | |||
| struct Recorder; | |||
| AudioIODeviceCallback* setCallback (AudioIODeviceCallback* const newCallback) | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| AudioIODeviceCallback* const oldCallback = callback; | |||
| callback = newCallback; | |||
| return oldCallback; | |||
| } | |||
| void run() override | |||
| { | |||
| if (recorder != nullptr) recorder->start(); | |||
| if (player != nullptr) player->start(); | |||
| while (! threadShouldExit()) | |||
| { | |||
| if (player != nullptr) player->writeBuffer (outputBuffer, *this); | |||
| if (recorder != nullptr) recorder->readNextBlock (inputBuffer, *this); | |||
| const ScopedLock sl (callbackLock); | |||
| if (callback != nullptr) | |||
| { | |||
| callback->audioDeviceIOCallback (numInputChannels > 0 ? inputBuffer.getArrayOfReadPointers() : nullptr, numInputChannels, | |||
| numOutputChannels > 0 ? outputBuffer.getArrayOfWritePointers() : nullptr, numOutputChannels, | |||
| actualBufferSize); | |||
| } | |||
| else | |||
| { | |||
| outputBuffer.clear(); | |||
| } | |||
| } | |||
| } | |||
| //================================================================================================== | |||
| struct Engine | |||
| { | |||
| Engine() | |||
| : engineObject (nullptr), engineInterface (nullptr), outputMixObject (nullptr) | |||
| { | |||
| if (library.open ("libOpenSLES.so")) | |||
| { | |||
| typedef SLresult (*CreateEngineFunc) (SLObjectItf*, SLuint32, const SLEngineOption*, SLuint32, const SLInterfaceID*, const SLboolean*); | |||
| if (CreateEngineFunc createEngine = (CreateEngineFunc) library.getFunction ("slCreateEngine")) | |||
| { | |||
| check (createEngine (&engineObject, 0, nullptr, 0, nullptr, nullptr)); | |||
| SLInterfaceID* SL_IID_ENGINE = (SLInterfaceID*) library.getFunction ("SL_IID_ENGINE"); | |||
| SL_IID_ANDROIDSIMPLEBUFFERQUEUE = (SLInterfaceID*) library.getFunction ("SL_IID_ANDROIDSIMPLEBUFFERQUEUE"); | |||
| SL_IID_PLAY = (SLInterfaceID*) library.getFunction ("SL_IID_PLAY"); | |||
| SL_IID_RECORD = (SLInterfaceID*) library.getFunction ("SL_IID_RECORD"); | |||
| SL_IID_ANDROIDCONFIGURATION = (SLInterfaceID*) library.getFunction ("SL_IID_ANDROIDCONFIGURATION"); | |||
| check ((*engineObject)->Realize (engineObject, SL_BOOLEAN_FALSE)); | |||
| check ((*engineObject)->GetInterface (engineObject, *SL_IID_ENGINE, &engineInterface)); | |||
| check ((*engineInterface)->CreateOutputMix (engineInterface, &outputMixObject, 0, nullptr, nullptr)); | |||
| check ((*outputMixObject)->Realize (outputMixObject, SL_BOOLEAN_FALSE)); | |||
| } | |||
| } | |||
| } | |||
| ~Engine() | |||
| { | |||
| if (outputMixObject != nullptr) (*outputMixObject)->Destroy (outputMixObject); | |||
| if (engineObject != nullptr) (*engineObject)->Destroy (engineObject); | |||
| } | |||
| Player* createPlayer (const int numChannels, const int sampleRate) | |||
| { | |||
| if (numChannels <= 0) | |||
| return nullptr; | |||
| ScopedPointer<Player> player (new Player (numChannels, sampleRate, *this)); | |||
| return player->openedOk() ? player.release() : nullptr; | |||
| } | |||
| Recorder* createRecorder (const int numChannels, const int sampleRate) | |||
| { | |||
| if (numChannels <= 0) | |||
| return nullptr; | |||
| ScopedPointer<Recorder> recorder (new Recorder (numChannels, sampleRate, *this)); | |||
| return recorder->openedOk() ? recorder.release() : nullptr; | |||
| } | |||
| SLObjectItf engineObject; | |||
| SLEngineItf engineInterface; | |||
| SLObjectItf outputMixObject; | |||
| SLInterfaceID* SL_IID_ANDROIDSIMPLEBUFFERQUEUE; | |||
| SLInterfaceID* SL_IID_PLAY; | |||
| SLInterfaceID* SL_IID_RECORD; | |||
| SLInterfaceID* SL_IID_ANDROIDCONFIGURATION; | |||
| private: | |||
| DynamicLibrary library; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Engine) | |||
| }; | |||
| //================================================================================================== | |||
| struct BufferList | |||
| { | |||
| BufferList (const int numChannels_) | |||
| : numChannels (numChannels_), bufferSpace (numChannels_ * numSamples * numBuffers), nextBlock (0) | |||
| { | |||
| } | |||
| int16* waitForFreeBuffer (Thread& threadToCheck) | |||
| { | |||
| while (numBlocksOut.get() == numBuffers) | |||
| { | |||
| dataArrived.wait (1); | |||
| if (threadToCheck.threadShouldExit()) | |||
| return nullptr; | |||
| } | |||
| return getNextBuffer(); | |||
| } | |||
| int16* getNextBuffer() | |||
| { | |||
| if (++nextBlock == numBuffers) | |||
| nextBlock = 0; | |||
| return bufferSpace + nextBlock * numChannels * numSamples; | |||
| } | |||
| void bufferReturned() { --numBlocksOut; dataArrived.signal(); } | |||
| void bufferSent() { ++numBlocksOut; dataArrived.signal(); } | |||
| int getBufferSizeBytes() const { return numChannels * numSamples * sizeof (int16); } | |||
| const int numChannels; | |||
| enum { numSamples = 256, numBuffers = 16 }; | |||
| private: | |||
| HeapBlock<int16> bufferSpace; | |||
| int nextBlock; | |||
| Atomic<int> numBlocksOut; | |||
| WaitableEvent dataArrived; | |||
| }; | |||
| //================================================================================================== | |||
| struct Player | |||
| { | |||
| Player (int numChannels, int sampleRate, Engine& engine) | |||
| : playerObject (nullptr), playerPlay (nullptr), playerBufferQueue (nullptr), | |||
| bufferList (numChannels) | |||
| { | |||
| jassert (numChannels == 2); | |||
| SLDataFormat_PCM pcmFormat = | |||
| { | |||
| SL_DATAFORMAT_PCM, | |||
| (SLuint32) numChannels, | |||
| (SLuint32) (sampleRate * 1000), // (sample rate units are millihertz) | |||
| SL_PCMSAMPLEFORMAT_FIXED_16, | |||
| SL_PCMSAMPLEFORMAT_FIXED_16, | |||
| SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, | |||
| SL_BYTEORDER_LITTLEENDIAN | |||
| }; | |||
| SLDataLocator_AndroidSimpleBufferQueue bufferQueue = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, bufferList.numBuffers }; | |||
| SLDataSource audioSrc = { &bufferQueue, &pcmFormat }; | |||
| SLDataLocator_OutputMix outputMix = { SL_DATALOCATOR_OUTPUTMIX, engine.outputMixObject }; | |||
| SLDataSink audioSink = { &outputMix, nullptr }; | |||
| // (SL_IID_BUFFERQUEUE is not guaranteed to remain future-proof, so use SL_IID_ANDROIDSIMPLEBUFFERQUEUE) | |||
| const SLInterfaceID interfaceIDs[] = { *engine.SL_IID_ANDROIDSIMPLEBUFFERQUEUE }; | |||
| const SLboolean flags[] = { SL_BOOLEAN_TRUE }; | |||
| check ((*engine.engineInterface)->CreateAudioPlayer (engine.engineInterface, &playerObject, &audioSrc, &audioSink, | |||
| 1, interfaceIDs, flags)); | |||
| check ((*playerObject)->Realize (playerObject, SL_BOOLEAN_FALSE)); | |||
| check ((*playerObject)->GetInterface (playerObject, *engine.SL_IID_PLAY, &playerPlay)); | |||
| check ((*playerObject)->GetInterface (playerObject, *engine.SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &playerBufferQueue)); | |||
| check ((*playerBufferQueue)->RegisterCallback (playerBufferQueue, staticCallback, this)); | |||
| } | |||
| ~Player() | |||
| { | |||
| if (playerPlay != nullptr) | |||
| check ((*playerPlay)->SetPlayState (playerPlay, SL_PLAYSTATE_STOPPED)); | |||
| if (playerBufferQueue != nullptr) | |||
| check ((*playerBufferQueue)->Clear (playerBufferQueue)); | |||
| if (playerObject != nullptr) | |||
| (*playerObject)->Destroy (playerObject); | |||
| } | |||
| bool openedOk() const noexcept { return playerBufferQueue != nullptr; } | |||
| void start() | |||
| { | |||
| jassert (openedOk()); | |||
| check ((*playerPlay)->SetPlayState (playerPlay, SL_PLAYSTATE_PLAYING)); | |||
| } | |||
| void writeBuffer (const AudioSampleBuffer& buffer, Thread& thread) | |||
| { | |||
| jassert (buffer.getNumChannels() == bufferList.numChannels); | |||
| jassert (buffer.getNumSamples() < bufferList.numSamples * bufferList.numBuffers); | |||
| int offset = 0; | |||
| int numSamples = buffer.getNumSamples(); | |||
| while (numSamples > 0) | |||
| { | |||
| int16* const destBuffer = bufferList.waitForFreeBuffer (thread); | |||
| if (destBuffer == nullptr) | |||
| break; | |||
| for (int i = 0; i < bufferList.numChannels; ++i) | |||
| { | |||
| typedef AudioData::Pointer <AudioData::Int16, AudioData::LittleEndian, AudioData::Interleaved, AudioData::NonConst> DstSampleType; | |||
| typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const> SrcSampleType; | |||
| DstSampleType dstData (destBuffer + i, bufferList.numChannels); | |||
| SrcSampleType srcData (buffer.getReadPointer (i, offset)); | |||
| dstData.convertSamples (srcData, bufferList.numSamples); | |||
| } | |||
| check ((*playerBufferQueue)->Enqueue (playerBufferQueue, destBuffer, bufferList.getBufferSizeBytes())); | |||
| bufferList.bufferSent(); | |||
| numSamples -= bufferList.numSamples; | |||
| offset += bufferList.numSamples; | |||
| } | |||
| } | |||
| private: | |||
| SLObjectItf playerObject; | |||
| SLPlayItf playerPlay; | |||
| SLAndroidSimpleBufferQueueItf playerBufferQueue; | |||
| BufferList bufferList; | |||
| static void staticCallback (SLAndroidSimpleBufferQueueItf queue, void* context) | |||
| { | |||
| jassert (queue == static_cast <Player*> (context)->playerBufferQueue); (void) queue; | |||
| static_cast <Player*> (context)->bufferList.bufferReturned(); | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Player) | |||
| }; | |||
| //================================================================================================== | |||
| struct Recorder | |||
| { | |||
| Recorder (int numChannels, int sampleRate, Engine& engine) | |||
| : recorderObject (nullptr), recorderRecord (nullptr), | |||
| recorderBufferQueue (nullptr), configObject (nullptr), | |||
| bufferList (numChannels) | |||
| { | |||
| jassert (numChannels == 1); // STEREO doesn't always work!! | |||
| SLDataFormat_PCM pcmFormat = | |||
| { | |||
| SL_DATAFORMAT_PCM, | |||
| (SLuint32) numChannels, | |||
| (SLuint32) (sampleRate * 1000), // (sample rate units are millihertz) | |||
| SL_PCMSAMPLEFORMAT_FIXED_16, | |||
| SL_PCMSAMPLEFORMAT_FIXED_16, | |||
| (numChannels == 1) ? SL_SPEAKER_FRONT_CENTER : (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT), | |||
| SL_BYTEORDER_LITTLEENDIAN | |||
| }; | |||
| SLDataLocator_IODevice ioDevice = { SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, nullptr }; | |||
| SLDataSource audioSrc = { &ioDevice, nullptr }; | |||
| SLDataLocator_AndroidSimpleBufferQueue bufferQueue = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, bufferList.numBuffers }; | |||
| SLDataSink audioSink = { &bufferQueue, &pcmFormat }; | |||
| const SLInterfaceID interfaceIDs[] = { *engine.SL_IID_ANDROIDSIMPLEBUFFERQUEUE }; | |||
| const SLboolean flags[] = { SL_BOOLEAN_TRUE }; | |||
| if (check ((*engine.engineInterface)->CreateAudioRecorder (engine.engineInterface, &recorderObject, &audioSrc, | |||
| &audioSink, 1, interfaceIDs, flags))) | |||
| { | |||
| if (check ((*recorderObject)->Realize (recorderObject, SL_BOOLEAN_FALSE))) | |||
| { | |||
| check ((*recorderObject)->GetInterface (recorderObject, *engine.SL_IID_RECORD, &recorderRecord)); | |||
| check ((*recorderObject)->GetInterface (recorderObject, *engine.SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &recorderBufferQueue)); | |||
| check ((*recorderObject)->GetInterface (recorderObject, *engine.SL_IID_ANDROIDCONFIGURATION, &configObject)); | |||
| check ((*recorderBufferQueue)->RegisterCallback (recorderBufferQueue, staticCallback, this)); | |||
| check ((*recorderRecord)->SetRecordState (recorderRecord, SL_RECORDSTATE_STOPPED)); | |||
| for (int i = bufferList.numBuffers; --i >= 0;) | |||
| { | |||
| int16* const buffer = bufferList.getNextBuffer(); | |||
| jassert (buffer != nullptr); | |||
| enqueueBuffer (buffer); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| ~Recorder() | |||
| { | |||
| if (recorderRecord != nullptr) | |||
| check ((*recorderRecord)->SetRecordState (recorderRecord, SL_RECORDSTATE_STOPPED)); | |||
| if (recorderBufferQueue != nullptr) | |||
| check ((*recorderBufferQueue)->Clear (recorderBufferQueue)); | |||
| if (recorderObject != nullptr) | |||
| (*recorderObject)->Destroy (recorderObject); | |||
| } | |||
| bool openedOk() const noexcept { return recorderBufferQueue != nullptr; } | |||
| void start() | |||
| { | |||
| jassert (openedOk()); | |||
| check ((*recorderRecord)->SetRecordState (recorderRecord, SL_RECORDSTATE_RECORDING)); | |||
| } | |||
| void readNextBlock (AudioSampleBuffer& buffer, Thread& thread) | |||
| { | |||
| jassert (buffer.getNumChannels() == bufferList.numChannels); | |||
| jassert (buffer.getNumSamples() < bufferList.numSamples * bufferList.numBuffers); | |||
| jassert ((buffer.getNumSamples() % bufferList.numSamples) == 0); | |||
| int offset = 0; | |||
| int numSamples = buffer.getNumSamples(); | |||
| while (numSamples > 0) | |||
| { | |||
| int16* const srcBuffer = bufferList.waitForFreeBuffer (thread); | |||
| if (srcBuffer == nullptr) | |||
| break; | |||
| for (int i = 0; i < bufferList.numChannels; ++i) | |||
| { | |||
| typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst> DstSampleType; | |||
| typedef AudioData::Pointer <AudioData::Int16, AudioData::LittleEndian, AudioData::Interleaved, AudioData::Const> SrcSampleType; | |||
| DstSampleType dstData (buffer.getWritePointer (i, offset)); | |||
| SrcSampleType srcData (srcBuffer + i, bufferList.numChannels); | |||
| dstData.convertSamples (srcData, bufferList.numSamples); | |||
| } | |||
| enqueueBuffer (srcBuffer); | |||
| numSamples -= bufferList.numSamples; | |||
| offset += bufferList.numSamples; | |||
| } | |||
| } | |||
| bool setAudioPreprocessingEnabled (bool enable) | |||
| { | |||
| SLuint32 mode = enable ? SL_ANDROID_RECORDING_PRESET_GENERIC | |||
| : SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION; | |||
| return configObject != nullptr | |||
| && check ((*configObject)->SetConfiguration (configObject, SL_ANDROID_KEY_RECORDING_PRESET, &mode, sizeof (mode))); | |||
| } | |||
| private: | |||
| SLObjectItf recorderObject; | |||
| SLRecordItf recorderRecord; | |||
| SLAndroidSimpleBufferQueueItf recorderBufferQueue; | |||
| SLAndroidConfigurationItf configObject; | |||
| BufferList bufferList; | |||
| void enqueueBuffer (int16* buffer) | |||
| { | |||
| check ((*recorderBufferQueue)->Enqueue (recorderBufferQueue, buffer, bufferList.getBufferSizeBytes())); | |||
| bufferList.bufferSent(); | |||
| } | |||
| static void staticCallback (SLAndroidSimpleBufferQueueItf queue, void* context) | |||
| { | |||
| jassert (queue == static_cast <Recorder*> (context)->recorderBufferQueue); (void) queue; | |||
| static_cast <Recorder*> (context)->bufferList.bufferReturned(); | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Recorder) | |||
| }; | |||
| //============================================================================== | |||
| Engine engine; | |||
| ScopedPointer<Player> player; | |||
| ScopedPointer<Recorder> recorder; | |||
| //============================================================================== | |||
| static bool check (const SLresult result) | |||
| { | |||
| jassert (result == SL_RESULT_SUCCESS); | |||
| return result == SL_RESULT_SUCCESS; | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenSLAudioIODevice) | |||
| }; | |||
| //============================================================================== | |||
| class OpenSLAudioDeviceType : public AudioIODeviceType | |||
| { | |||
| public: | |||
| OpenSLAudioDeviceType() : AudioIODeviceType (openSLTypeName) {} | |||
| //============================================================================== | |||
| void scanForDevices() {} | |||
| StringArray getDeviceNames (bool wantInputNames) const { return StringArray (openSLTypeName); } | |||
| int getDefaultDeviceIndex (bool forInput) const { return 0; } | |||
| int getIndexOfDevice (AudioIODevice* device, bool asInput) const { return device != nullptr ? 0 : -1; } | |||
| bool hasSeparateInputsAndOutputs() const { return false; } | |||
| AudioIODevice* createDevice (const String& outputDeviceName, | |||
| const String& inputDeviceName) | |||
| { | |||
| ScopedPointer<OpenSLAudioIODevice> dev; | |||
| if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty()) | |||
| { | |||
| dev = new OpenSLAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName | |||
| : inputDeviceName); | |||
| if (! dev->openedOk()) | |||
| dev = nullptr; | |||
| } | |||
| return dev.release(); | |||
| } | |||
| private: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenSLAudioDeviceType) | |||
| }; | |||
| //============================================================================== | |||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES() | |||
| { | |||
| return isOpenSLAvailable() ? new OpenSLAudioDeviceType() : nullptr; | |||
| } | |||
| @@ -1,576 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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. | |||
| ============================================================================== | |||
| */ | |||
| class iOSAudioIODevice : public AudioIODevice | |||
| { | |||
| public: | |||
| iOSAudioIODevice (const String& deviceName) | |||
| : AudioIODevice (deviceName, "Audio"), | |||
| actualBufferSize (0), | |||
| isRunning (false), | |||
| audioUnit (0), | |||
| callback (nullptr), | |||
| floatData (1, 2) | |||
| { | |||
| getSessionHolder().activeDevices.add (this); | |||
| numInputChannels = 2; | |||
| numOutputChannels = 2; | |||
| preferredBufferSize = 0; | |||
| updateDeviceInfo(); | |||
| } | |||
| ~iOSAudioIODevice() | |||
| { | |||
| getSessionHolder().activeDevices.removeFirstMatchingValue (this); | |||
| close(); | |||
| } | |||
| StringArray getOutputChannelNames() override | |||
| { | |||
| StringArray s; | |||
| s.add ("Left"); | |||
| s.add ("Right"); | |||
| return s; | |||
| } | |||
| StringArray getInputChannelNames() override | |||
| { | |||
| StringArray s; | |||
| if (audioInputIsAvailable) | |||
| { | |||
| s.add ("Left"); | |||
| s.add ("Right"); | |||
| } | |||
| return s; | |||
| } | |||
| Array<double> getAvailableSampleRates() override | |||
| { | |||
| // can't find a good way to actually ask the device for which of these it supports.. | |||
| static const double rates[] = { 8000.0, 16000.0, 22050.0, 32000.0, 44100.0, 48000.0 }; | |||
| return Array<double> (rates, numElementsInArray (rates)); | |||
| } | |||
| Array<int> getAvailableBufferSizes() override | |||
| { | |||
| Array<int> r; | |||
| for (int i = 6; i < 12; ++i) | |||
| r.add (1 << i); | |||
| return r; | |||
| } | |||
| int getDefaultBufferSize() override { return 1024; } | |||
| String open (const BigInteger& inputChannelsWanted, | |||
| const BigInteger& outputChannelsWanted, | |||
| double targetSampleRate, int bufferSize) override | |||
| { | |||
| close(); | |||
| lastError.clear(); | |||
| preferredBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize; | |||
| // xxx set up channel mapping | |||
| activeOutputChans = outputChannelsWanted; | |||
| activeOutputChans.setRange (2, activeOutputChans.getHighestBit(), false); | |||
| numOutputChannels = activeOutputChans.countNumberOfSetBits(); | |||
| monoOutputChannelNumber = activeOutputChans.findNextSetBit (0); | |||
| activeInputChans = inputChannelsWanted; | |||
| activeInputChans.setRange (2, activeInputChans.getHighestBit(), false); | |||
| numInputChannels = activeInputChans.countNumberOfSetBits(); | |||
| monoInputChannelNumber = activeInputChans.findNextSetBit (0); | |||
| AudioSessionSetActive (true); | |||
| if (numInputChannels > 0 && audioInputIsAvailable) | |||
| { | |||
| setSessionUInt32Property (kAudioSessionProperty_AudioCategory, kAudioSessionCategory_PlayAndRecord); | |||
| setSessionUInt32Property (kAudioSessionProperty_OverrideCategoryEnableBluetoothInput, 1); | |||
| } | |||
| else | |||
| { | |||
| setSessionUInt32Property (kAudioSessionProperty_AudioCategory, kAudioSessionCategory_MediaPlayback); | |||
| } | |||
| AudioSessionAddPropertyListener (kAudioSessionProperty_AudioRouteChange, routingChangedStatic, this); | |||
| fixAudioRouteIfSetToReceiver(); | |||
| setSessionFloat64Property (kAudioSessionProperty_PreferredHardwareSampleRate, targetSampleRate); | |||
| updateDeviceInfo(); | |||
| setSessionFloat32Property (kAudioSessionProperty_PreferredHardwareIOBufferDuration, preferredBufferSize / sampleRate); | |||
| updateCurrentBufferSize(); | |||
| prepareFloatBuffers (actualBufferSize); | |||
| isRunning = true; | |||
| routingChanged (nullptr); // creates and starts the AU | |||
| lastError = audioUnit != 0 ? "" : "Couldn't open the device"; | |||
| return lastError; | |||
| } | |||
| void close() override | |||
| { | |||
| if (isRunning) | |||
| { | |||
| isRunning = false; | |||
| setSessionUInt32Property (kAudioSessionProperty_AudioCategory, kAudioSessionCategory_MediaPlayback); | |||
| AudioSessionRemovePropertyListenerWithUserData (kAudioSessionProperty_AudioRouteChange, routingChangedStatic, this); | |||
| AudioSessionSetActive (false); | |||
| if (audioUnit != 0) | |||
| { | |||
| AudioComponentInstanceDispose (audioUnit); | |||
| audioUnit = 0; | |||
| } | |||
| } | |||
| } | |||
| bool isOpen() override { return isRunning; } | |||
| int getCurrentBufferSizeSamples() override { return actualBufferSize; } | |||
| double getCurrentSampleRate() override { return sampleRate; } | |||
| int getCurrentBitDepth() override { return 16; } | |||
| BigInteger getActiveOutputChannels() const override { return activeOutputChans; } | |||
| BigInteger getActiveInputChannels() const override { return activeInputChans; } | |||
| int getOutputLatencyInSamples() override { return getLatency (kAudioSessionProperty_CurrentHardwareOutputLatency); } | |||
| int getInputLatencyInSamples() override { return getLatency (kAudioSessionProperty_CurrentHardwareInputLatency); } | |||
| int getLatency (AudioSessionPropertyID propID) | |||
| { | |||
| Float32 latency = 0; | |||
| getSessionProperty (propID, latency); | |||
| return roundToInt (latency * getCurrentSampleRate()); | |||
| } | |||
| void start (AudioIODeviceCallback* newCallback) override | |||
| { | |||
| if (isRunning && callback != newCallback) | |||
| { | |||
| if (newCallback != nullptr) | |||
| newCallback->audioDeviceAboutToStart (this); | |||
| const ScopedLock sl (callbackLock); | |||
| callback = newCallback; | |||
| } | |||
| } | |||
| void stop() override | |||
| { | |||
| if (isRunning) | |||
| { | |||
| AudioIODeviceCallback* lastCallback; | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| lastCallback = callback; | |||
| callback = nullptr; | |||
| } | |||
| if (lastCallback != nullptr) | |||
| lastCallback->audioDeviceStopped(); | |||
| } | |||
| } | |||
| bool isPlaying() override { return isRunning && callback != nullptr; } | |||
| String getLastError() override { return lastError; } | |||
| bool setAudioPreprocessingEnabled (bool enable) override | |||
| { | |||
| return setSessionUInt32Property (kAudioSessionProperty_Mode, enable ? kAudioSessionMode_Default | |||
| : kAudioSessionMode_Measurement); | |||
| } | |||
| private: | |||
| //================================================================================================== | |||
| CriticalSection callbackLock; | |||
| Float64 sampleRate; | |||
| int numInputChannels, numOutputChannels; | |||
| int preferredBufferSize, actualBufferSize; | |||
| bool isRunning; | |||
| String lastError; | |||
| AudioStreamBasicDescription format; | |||
| AudioUnit audioUnit; | |||
| UInt32 audioInputIsAvailable; | |||
| AudioIODeviceCallback* callback; | |||
| BigInteger activeOutputChans, activeInputChans; | |||
| AudioSampleBuffer floatData; | |||
| float* inputChannels[3]; | |||
| float* outputChannels[3]; | |||
| bool monoInputChannelNumber, monoOutputChannelNumber; | |||
| void prepareFloatBuffers (int bufferSize) | |||
| { | |||
| if (numInputChannels + numOutputChannels > 0) | |||
| { | |||
| floatData.setSize (numInputChannels + numOutputChannels, bufferSize); | |||
| zeromem (inputChannels, sizeof (inputChannels)); | |||
| zeromem (outputChannels, sizeof (outputChannels)); | |||
| for (int i = 0; i < numInputChannels; ++i) | |||
| inputChannels[i] = floatData.getWritePointer (i); | |||
| for (int i = 0; i < numOutputChannels; ++i) | |||
| outputChannels[i] = floatData.getWritePointer (i + numInputChannels); | |||
| } | |||
| } | |||
| //================================================================================================== | |||
| OSStatus process (AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time, | |||
| const UInt32 numFrames, AudioBufferList* data) | |||
| { | |||
| OSStatus err = noErr; | |||
| if (audioInputIsAvailable && numInputChannels > 0) | |||
| err = AudioUnitRender (audioUnit, flags, time, 1, numFrames, data); | |||
| const ScopedLock sl (callbackLock); | |||
| if (callback != nullptr) | |||
| { | |||
| if ((int) numFrames > floatData.getNumSamples()) | |||
| prepareFloatBuffers ((int) numFrames); | |||
| if (audioInputIsAvailable && numInputChannels > 0) | |||
| { | |||
| short* shortData = (short*) data->mBuffers[0].mData; | |||
| if (numInputChannels >= 2) | |||
| { | |||
| for (UInt32 i = 0; i < numFrames; ++i) | |||
| { | |||
| inputChannels[0][i] = *shortData++ * (1.0f / 32768.0f); | |||
| inputChannels[1][i] = *shortData++ * (1.0f / 32768.0f); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| if (monoInputChannelNumber > 0) | |||
| ++shortData; | |||
| for (UInt32 i = 0; i < numFrames; ++i) | |||
| { | |||
| inputChannels[0][i] = *shortData++ * (1.0f / 32768.0f); | |||
| ++shortData; | |||
| } | |||
| } | |||
| } | |||
| else | |||
| { | |||
| for (int i = numInputChannels; --i >= 0;) | |||
| zeromem (inputChannels[i], sizeof (float) * numFrames); | |||
| } | |||
| callback->audioDeviceIOCallback ((const float**) inputChannels, numInputChannels, | |||
| outputChannels, numOutputChannels, (int) numFrames); | |||
| short* shortData = (short*) data->mBuffers[0].mData; | |||
| int n = 0; | |||
| if (numOutputChannels >= 2) | |||
| { | |||
| for (UInt32 i = 0; i < numFrames; ++i) | |||
| { | |||
| shortData [n++] = (short) (outputChannels[0][i] * 32767.0f); | |||
| shortData [n++] = (short) (outputChannels[1][i] * 32767.0f); | |||
| } | |||
| } | |||
| else if (numOutputChannels == 1) | |||
| { | |||
| for (UInt32 i = 0; i < numFrames; ++i) | |||
| { | |||
| const short s = (short) (outputChannels[monoOutputChannelNumber][i] * 32767.0f); | |||
| shortData [n++] = s; | |||
| shortData [n++] = s; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| zeromem (data->mBuffers[0].mData, 2 * sizeof (short) * numFrames); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| zeromem (data->mBuffers[0].mData, 2 * sizeof (short) * numFrames); | |||
| } | |||
| return err; | |||
| } | |||
| void updateDeviceInfo() | |||
| { | |||
| getSessionProperty (kAudioSessionProperty_CurrentHardwareSampleRate, sampleRate); | |||
| getSessionProperty (kAudioSessionProperty_AudioInputAvailable, audioInputIsAvailable); | |||
| } | |||
| void updateCurrentBufferSize() | |||
| { | |||
| Float32 bufferDuration = sampleRate > 0 ? (Float32) (preferredBufferSize / sampleRate) : 0.0f; | |||
| getSessionProperty (kAudioSessionProperty_CurrentHardwareIOBufferDuration, bufferDuration); | |||
| actualBufferSize = (int) (sampleRate * bufferDuration + 0.5); | |||
| } | |||
| void routingChanged (const void* propertyValue) | |||
| { | |||
| if (! isRunning) | |||
| return; | |||
| if (propertyValue != nullptr) | |||
| { | |||
| CFDictionaryRef routeChangeDictionary = (CFDictionaryRef) propertyValue; | |||
| CFNumberRef routeChangeReasonRef = (CFNumberRef) CFDictionaryGetValue (routeChangeDictionary, | |||
| CFSTR (kAudioSession_AudioRouteChangeKey_Reason)); | |||
| SInt32 routeChangeReason; | |||
| CFNumberGetValue (routeChangeReasonRef, kCFNumberSInt32Type, &routeChangeReason); | |||
| if (routeChangeReason == kAudioSessionRouteChangeReason_OldDeviceUnavailable) | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| if (callback != nullptr) | |||
| callback->audioDeviceError ("Old device unavailable"); | |||
| } | |||
| } | |||
| updateDeviceInfo(); | |||
| createAudioUnit(); | |||
| AudioSessionSetActive (true); | |||
| if (audioUnit != 0) | |||
| { | |||
| UInt32 formatSize = sizeof (format); | |||
| AudioUnitGetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, &formatSize); | |||
| updateCurrentBufferSize(); | |||
| AudioOutputUnitStart (audioUnit); | |||
| } | |||
| } | |||
| //================================================================================================== | |||
| struct AudioSessionHolder | |||
| { | |||
| AudioSessionHolder() | |||
| { | |||
| AudioSessionInitialize (0, 0, interruptionListenerCallback, this); | |||
| } | |||
| static void interruptionListenerCallback (void* client, UInt32 interruptionType) | |||
| { | |||
| const Array <iOSAudioIODevice*>& activeDevices = static_cast <AudioSessionHolder*> (client)->activeDevices; | |||
| for (int i = activeDevices.size(); --i >= 0;) | |||
| activeDevices.getUnchecked(i)->interruptionListener (interruptionType); | |||
| } | |||
| Array <iOSAudioIODevice*> activeDevices; | |||
| }; | |||
| static AudioSessionHolder& getSessionHolder() | |||
| { | |||
| static AudioSessionHolder audioSessionHolder; | |||
| return audioSessionHolder; | |||
| } | |||
| void interruptionListener (const UInt32 interruptionType) | |||
| { | |||
| if (interruptionType == kAudioSessionBeginInterruption) | |||
| { | |||
| isRunning = false; | |||
| AudioOutputUnitStop (audioUnit); | |||
| AudioSessionSetActive (false); | |||
| const ScopedLock sl (callbackLock); | |||
| if (callback != nullptr) | |||
| callback->audioDeviceError ("iOS audio session interruption"); | |||
| } | |||
| if (interruptionType == kAudioSessionEndInterruption) | |||
| { | |||
| isRunning = true; | |||
| AudioSessionSetActive (true); | |||
| AudioOutputUnitStart (audioUnit); | |||
| const ScopedLock sl (callbackLock); | |||
| if (callback != nullptr) | |||
| callback->audioDeviceError ("iOS audio session resumed"); | |||
| } | |||
| } | |||
| //================================================================================================== | |||
| static OSStatus processStatic (void* client, AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time, | |||
| UInt32 /*busNumber*/, UInt32 numFrames, AudioBufferList* data) | |||
| { | |||
| return static_cast<iOSAudioIODevice*> (client)->process (flags, time, numFrames, data); | |||
| } | |||
| static void routingChangedStatic (void* client, AudioSessionPropertyID, UInt32 /*inDataSize*/, const void* propertyValue) | |||
| { | |||
| static_cast<iOSAudioIODevice*> (client)->routingChanged (propertyValue); | |||
| } | |||
| //================================================================================================== | |||
| void resetFormat (const int numChannels) noexcept | |||
| { | |||
| zerostruct (format); | |||
| format.mFormatID = kAudioFormatLinearPCM; | |||
| format.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked | kAudioFormatFlagsNativeEndian; | |||
| format.mBitsPerChannel = 8 * sizeof (short); | |||
| format.mChannelsPerFrame = (UInt32) numChannels; | |||
| format.mFramesPerPacket = 1; | |||
| format.mBytesPerFrame = format.mBytesPerPacket = (UInt32) numChannels * sizeof (short); | |||
| } | |||
| bool createAudioUnit() | |||
| { | |||
| if (audioUnit != 0) | |||
| { | |||
| AudioComponentInstanceDispose (audioUnit); | |||
| audioUnit = 0; | |||
| } | |||
| resetFormat (2); | |||
| AudioComponentDescription desc; | |||
| desc.componentType = kAudioUnitType_Output; | |||
| desc.componentSubType = kAudioUnitSubType_RemoteIO; | |||
| desc.componentManufacturer = kAudioUnitManufacturer_Apple; | |||
| desc.componentFlags = 0; | |||
| desc.componentFlagsMask = 0; | |||
| AudioComponent comp = AudioComponentFindNext (0, &desc); | |||
| AudioComponentInstanceNew (comp, &audioUnit); | |||
| if (audioUnit == 0) | |||
| return false; | |||
| if (numInputChannels > 0) | |||
| { | |||
| const UInt32 one = 1; | |||
| AudioUnitSetProperty (audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &one, sizeof (one)); | |||
| } | |||
| { | |||
| AudioChannelLayout layout; | |||
| layout.mChannelBitmap = 0; | |||
| layout.mNumberChannelDescriptions = 0; | |||
| layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo; | |||
| AudioUnitSetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Input, 0, &layout, sizeof (layout)); | |||
| AudioUnitSetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Output, 0, &layout, sizeof (layout)); | |||
| } | |||
| { | |||
| AURenderCallbackStruct inputProc; | |||
| inputProc.inputProc = processStatic; | |||
| inputProc.inputProcRefCon = this; | |||
| AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &inputProc, sizeof (inputProc)); | |||
| } | |||
| AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, sizeof (format)); | |||
| AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, sizeof (format)); | |||
| AudioUnitInitialize (audioUnit); | |||
| return true; | |||
| } | |||
| // If the routing is set to go through the receiver (i.e. the speaker, but quiet), this re-routes it | |||
| // to make it loud. Needed because by default when using an input + output, the output is kept quiet. | |||
| static void fixAudioRouteIfSetToReceiver() | |||
| { | |||
| CFStringRef audioRoute = 0; | |||
| if (getSessionProperty (kAudioSessionProperty_AudioRoute, audioRoute) == noErr) | |||
| { | |||
| NSString* route = (NSString*) audioRoute; | |||
| //DBG ("audio route: " + nsStringToJuce (route)); | |||
| if ([route hasPrefix: @"Receiver"]) | |||
| setSessionUInt32Property (kAudioSessionProperty_OverrideAudioRoute, kAudioSessionOverrideAudioRoute_Speaker); | |||
| CFRelease (audioRoute); | |||
| } | |||
| } | |||
| template <typename Type> | |||
| static OSStatus getSessionProperty (AudioSessionPropertyID propID, Type& result) noexcept | |||
| { | |||
| UInt32 valueSize = sizeof (result); | |||
| return AudioSessionGetProperty (propID, &valueSize, &result); | |||
| } | |||
| static bool setSessionUInt32Property (AudioSessionPropertyID propID, UInt32 v) noexcept { return AudioSessionSetProperty (propID, sizeof (v), &v) == kAudioSessionNoError; } | |||
| static bool setSessionFloat32Property (AudioSessionPropertyID propID, Float32 v) noexcept { return AudioSessionSetProperty (propID, sizeof (v), &v) == kAudioSessionNoError; } | |||
| static bool setSessionFloat64Property (AudioSessionPropertyID propID, Float64 v) noexcept { return AudioSessionSetProperty (propID, sizeof (v), &v) == kAudioSessionNoError; } | |||
| JUCE_DECLARE_NON_COPYABLE (iOSAudioIODevice) | |||
| }; | |||
| //============================================================================== | |||
| class iOSAudioIODeviceType : public AudioIODeviceType | |||
| { | |||
| public: | |||
| iOSAudioIODeviceType() : AudioIODeviceType ("iOS Audio") {} | |||
| void scanForDevices() {} | |||
| StringArray getDeviceNames (bool /*wantInputNames*/) const { return StringArray ("iOS Audio"); } | |||
| int getDefaultDeviceIndex (bool /*forInput*/) const { return 0; } | |||
| int getIndexOfDevice (AudioIODevice* d, bool /*asInput*/) const { return d != nullptr ? 0 : -1; } | |||
| bool hasSeparateInputsAndOutputs() const { return false; } | |||
| AudioIODevice* createDevice (const String& outputDeviceName, const String& inputDeviceName) | |||
| { | |||
| if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty()) | |||
| return new iOSAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName | |||
| : inputDeviceName); | |||
| return nullptr; | |||
| } | |||
| private: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSAudioIODeviceType) | |||
| }; | |||
| //============================================================================== | |||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio() | |||
| { | |||
| return new iOSAudioIODeviceType(); | |||
| } | |||
| @@ -1,77 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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. | |||
| ============================================================================== | |||
| */ | |||
| AudioCDReader::AudioCDReader() | |||
| : AudioFormatReader (0, "CD Audio") | |||
| { | |||
| } | |||
| StringArray AudioCDReader::getAvailableCDNames() | |||
| { | |||
| StringArray names; | |||
| return names; | |||
| } | |||
| AudioCDReader* AudioCDReader::createReaderForCD (const int index) | |||
| { | |||
| return nullptr; | |||
| } | |||
| AudioCDReader::~AudioCDReader() | |||
| { | |||
| } | |||
| void AudioCDReader::refreshTrackLengths() | |||
| { | |||
| } | |||
| bool AudioCDReader::readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | |||
| int64 startSampleInFile, int numSamples) | |||
| { | |||
| return false; | |||
| } | |||
| bool AudioCDReader::isCDStillPresent() const | |||
| { | |||
| return false; | |||
| } | |||
| bool AudioCDReader::isTrackAudio (int trackNum) const | |||
| { | |||
| return false; | |||
| } | |||
| void AudioCDReader::enableIndexScanning (bool b) | |||
| { | |||
| } | |||
| int AudioCDReader::getLastIndex() const | |||
| { | |||
| return 0; | |||
| } | |||
| Array<int> AudioCDReader::findIndexesInTrack (const int trackNumber) | |||
| { | |||
| return Array<int>(); | |||
| } | |||
| @@ -1,604 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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. | |||
| ============================================================================== | |||
| */ | |||
| //============================================================================== | |||
| static void* juce_libjackHandle = nullptr; | |||
| static void* juce_loadJackFunction (const char* const name) | |||
| { | |||
| if (juce_libjackHandle == nullptr) | |||
| return nullptr; | |||
| return dlsym (juce_libjackHandle, name); | |||
| } | |||
| #define JUCE_DECL_JACK_FUNCTION(return_type, fn_name, argument_types, arguments) \ | |||
| return_type fn_name argument_types \ | |||
| { \ | |||
| typedef return_type (*fn_type) argument_types; \ | |||
| static fn_type fn = (fn_type) juce_loadJackFunction (#fn_name); \ | |||
| return (fn != nullptr) ? ((*fn) arguments) : (return_type) 0; \ | |||
| } | |||
| #define JUCE_DECL_VOID_JACK_FUNCTION(fn_name, argument_types, arguments) \ | |||
| void fn_name argument_types \ | |||
| { \ | |||
| typedef void (*fn_type) argument_types; \ | |||
| static fn_type fn = (fn_type) juce_loadJackFunction (#fn_name); \ | |||
| if (fn != nullptr) (*fn) arguments; \ | |||
| } | |||
| //============================================================================== | |||
| JUCE_DECL_JACK_FUNCTION (jack_client_t*, jack_client_open, (const char* client_name, jack_options_t options, jack_status_t* status, ...), (client_name, options, status)); | |||
| JUCE_DECL_JACK_FUNCTION (int, jack_client_close, (jack_client_t *client), (client)); | |||
| JUCE_DECL_JACK_FUNCTION (int, jack_activate, (jack_client_t* client), (client)); | |||
| JUCE_DECL_JACK_FUNCTION (int, jack_deactivate, (jack_client_t* client), (client)); | |||
| JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_get_buffer_size, (jack_client_t* client), (client)); | |||
| JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_get_sample_rate, (jack_client_t* client), (client)); | |||
| JUCE_DECL_VOID_JACK_FUNCTION (jack_on_shutdown, (jack_client_t* client, void (*function)(void* arg), void* arg), (client, function, arg)); | |||
| JUCE_DECL_JACK_FUNCTION (void* , jack_port_get_buffer, (jack_port_t* port, jack_nframes_t nframes), (port, nframes)); | |||
| JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_port_get_total_latency, (jack_client_t* client, jack_port_t* port), (client, port)); | |||
| JUCE_DECL_JACK_FUNCTION (jack_port_t* , jack_port_register, (jack_client_t* client, const char* port_name, const char* port_type, unsigned long flags, unsigned long buffer_size), (client, port_name, port_type, flags, buffer_size)); | |||
| JUCE_DECL_VOID_JACK_FUNCTION (jack_set_error_function, (void (*func)(const char*)), (func)); | |||
| JUCE_DECL_JACK_FUNCTION (int, jack_set_process_callback, (jack_client_t* client, JackProcessCallback process_callback, void* arg), (client, process_callback, arg)); | |||
| JUCE_DECL_JACK_FUNCTION (const char**, jack_get_ports, (jack_client_t* client, const char* port_name_pattern, const char* type_name_pattern, unsigned long flags), (client, port_name_pattern, type_name_pattern, flags)); | |||
| JUCE_DECL_JACK_FUNCTION (int, jack_connect, (jack_client_t* client, const char* source_port, const char* destination_port), (client, source_port, destination_port)); | |||
| JUCE_DECL_JACK_FUNCTION (const char*, jack_port_name, (const jack_port_t* port), (port)); | |||
| JUCE_DECL_JACK_FUNCTION (void*, jack_set_port_connect_callback, (jack_client_t* client, JackPortConnectCallback connect_callback, void* arg), (client, connect_callback, arg)); | |||
| JUCE_DECL_JACK_FUNCTION (jack_port_t* , jack_port_by_id, (jack_client_t* client, jack_port_id_t port_id), (client, port_id)); | |||
| JUCE_DECL_JACK_FUNCTION (int, jack_port_connected, (const jack_port_t* port), (port)); | |||
| JUCE_DECL_JACK_FUNCTION (int, jack_port_connected_to, (const jack_port_t* port, const char* port_name), (port, port_name)); | |||
| #if JUCE_DEBUG | |||
| #define JACK_LOGGING_ENABLED 1 | |||
| #endif | |||
| #if JACK_LOGGING_ENABLED | |||
| namespace | |||
| { | |||
| void jack_Log (const String& s) | |||
| { | |||
| std::cerr << s << std::endl; | |||
| } | |||
| const char* getJackErrorMessage (const jack_status_t status) | |||
| { | |||
| if (status & JackServerFailed | |||
| || status & JackServerError) return "Unable to connect to JACK server"; | |||
| if (status & JackVersionError) return "Client's protocol version does not match"; | |||
| if (status & JackInvalidOption) return "The operation contained an invalid or unsupported option"; | |||
| if (status & JackNameNotUnique) return "The desired client name was not unique"; | |||
| if (status & JackNoSuchClient) return "Requested client does not exist"; | |||
| if (status & JackInitFailure) return "Unable to initialize client"; | |||
| return nullptr; | |||
| } | |||
| } | |||
| #define JUCE_JACK_LOG_STATUS(x) { if (const char* m = getJackErrorMessage (x)) jack_Log (m); } | |||
| #define JUCE_JACK_LOG(x) jack_Log(x) | |||
| #else | |||
| #define JUCE_JACK_LOG_STATUS(x) {} | |||
| #define JUCE_JACK_LOG(x) {} | |||
| #endif | |||
| //============================================================================== | |||
| #ifndef JUCE_JACK_CLIENT_NAME | |||
| #define JUCE_JACK_CLIENT_NAME "JUCEJack" | |||
| #endif | |||
| struct JackPortIterator | |||
| { | |||
| JackPortIterator (jack_client_t* const client, const bool forInput) | |||
| : ports (nullptr), index (-1) | |||
| { | |||
| if (client != nullptr) | |||
| ports = juce::jack_get_ports (client, nullptr, nullptr, | |||
| forInput ? JackPortIsOutput : JackPortIsInput); | |||
| // (NB: This looks like it's the wrong way round, but it is correct!) | |||
| } | |||
| ~JackPortIterator() | |||
| { | |||
| ::free (ports); | |||
| } | |||
| bool next() | |||
| { | |||
| if (ports == nullptr || ports [index + 1] == nullptr) | |||
| return false; | |||
| name = CharPointer_UTF8 (ports[++index]); | |||
| clientName = name.upToFirstOccurrenceOf (":", false, false); | |||
| return true; | |||
| } | |||
| const char** ports; | |||
| int index; | |||
| String name; | |||
| String clientName; | |||
| }; | |||
| class JackAudioIODeviceType; | |||
| static Array<JackAudioIODeviceType*> activeDeviceTypes; | |||
| //============================================================================== | |||
| class JackAudioIODevice : public AudioIODevice | |||
| { | |||
| public: | |||
| JackAudioIODevice (const String& deviceName, | |||
| const String& inId, | |||
| const String& outId) | |||
| : AudioIODevice (deviceName, "JACK"), | |||
| inputId (inId), | |||
| outputId (outId), | |||
| deviceIsOpen (false), | |||
| callback (nullptr), | |||
| totalNumberOfInputChannels (0), | |||
| totalNumberOfOutputChannels (0) | |||
| { | |||
| jassert (deviceName.isNotEmpty()); | |||
| jack_status_t status; | |||
| client = juce::jack_client_open (JUCE_JACK_CLIENT_NAME, JackNoStartServer, &status); | |||
| if (client == nullptr) | |||
| { | |||
| JUCE_JACK_LOG_STATUS (status); | |||
| } | |||
| else | |||
| { | |||
| juce::jack_set_error_function (errorCallback); | |||
| // open input ports | |||
| const StringArray inputChannels (getInputChannelNames()); | |||
| for (int i = 0; i < inputChannels.size(); ++i) | |||
| { | |||
| String inputName; | |||
| inputName << "in_" << ++totalNumberOfInputChannels; | |||
| inputPorts.add (juce::jack_port_register (client, inputName.toUTF8(), | |||
| JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0)); | |||
| } | |||
| // open output ports | |||
| const StringArray outputChannels (getOutputChannelNames()); | |||
| for (int i = 0; i < outputChannels.size (); ++i) | |||
| { | |||
| String outputName; | |||
| outputName << "out_" << ++totalNumberOfOutputChannels; | |||
| outputPorts.add (juce::jack_port_register (client, outputName.toUTF8(), | |||
| JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0)); | |||
| } | |||
| inChans.calloc (totalNumberOfInputChannels + 2); | |||
| outChans.calloc (totalNumberOfOutputChannels + 2); | |||
| } | |||
| } | |||
| ~JackAudioIODevice() | |||
| { | |||
| close(); | |||
| if (client != nullptr) | |||
| { | |||
| juce::jack_client_close (client); | |||
| client = nullptr; | |||
| } | |||
| } | |||
| StringArray getChannelNames (bool forInput) const | |||
| { | |||
| StringArray names; | |||
| for (JackPortIterator i (client, forInput); i.next();) | |||
| if (i.clientName == getName()) | |||
| names.add (i.name.fromFirstOccurrenceOf (":", false, false)); | |||
| return names; | |||
| } | |||
| StringArray getOutputChannelNames() override { return getChannelNames (false); } | |||
| StringArray getInputChannelNames() override { return getChannelNames (true); } | |||
| Array<double> getAvailableSampleRates() override | |||
| { | |||
| Array<double> rates; | |||
| if (client != nullptr) | |||
| rates.add (juce::jack_get_sample_rate (client)); | |||
| return rates; | |||
| } | |||
| Array<int> getAvailableBufferSizes() override | |||
| { | |||
| Array<int> sizes; | |||
| if (client != nullptr) | |||
| sizes.add (juce::jack_get_buffer_size (client)); | |||
| return sizes; | |||
| } | |||
| int getDefaultBufferSize() override { return getCurrentBufferSizeSamples(); } | |||
| int getCurrentBufferSizeSamples() override { return client != nullptr ? juce::jack_get_buffer_size (client) : 0; } | |||
| double getCurrentSampleRate() override { return client != nullptr ? juce::jack_get_sample_rate (client) : 0; } | |||
| String open (const BigInteger& inputChannels, const BigInteger& outputChannels, | |||
| double /* sampleRate */, int /* bufferSizeSamples */) override | |||
| { | |||
| if (client == nullptr) | |||
| { | |||
| lastError = "No JACK client running"; | |||
| return lastError; | |||
| } | |||
| lastError.clear(); | |||
| close(); | |||
| juce::jack_set_process_callback (client, processCallback, this); | |||
| juce::jack_set_port_connect_callback (client, portConnectCallback, this); | |||
| juce::jack_on_shutdown (client, shutdownCallback, this); | |||
| juce::jack_activate (client); | |||
| deviceIsOpen = true; | |||
| if (! inputChannels.isZero()) | |||
| { | |||
| for (JackPortIterator i (client, true); i.next();) | |||
| { | |||
| if (inputChannels [i.index] && i.clientName == getName()) | |||
| { | |||
| int error = juce::jack_connect (client, i.ports[i.index], juce::jack_port_name ((jack_port_t*) inputPorts[i.index])); | |||
| if (error != 0) | |||
| JUCE_JACK_LOG ("Cannot connect input port " + String (i.index) + " (" + i.name + "), error " + String (error)); | |||
| } | |||
| } | |||
| } | |||
| if (! outputChannels.isZero()) | |||
| { | |||
| for (JackPortIterator i (client, false); i.next();) | |||
| { | |||
| if (outputChannels [i.index] && i.clientName == getName()) | |||
| { | |||
| int error = juce::jack_connect (client, juce::jack_port_name ((jack_port_t*) outputPorts[i.index]), i.ports[i.index]); | |||
| if (error != 0) | |||
| JUCE_JACK_LOG ("Cannot connect output port " + String (i.index) + " (" + i.name + "), error " + String (error)); | |||
| } | |||
| } | |||
| } | |||
| return lastError; | |||
| } | |||
| void close() override | |||
| { | |||
| stop(); | |||
| if (client != nullptr) | |||
| { | |||
| juce::jack_deactivate (client); | |||
| juce::jack_set_process_callback (client, processCallback, nullptr); | |||
| juce::jack_set_port_connect_callback (client, portConnectCallback, nullptr); | |||
| juce::jack_on_shutdown (client, shutdownCallback, nullptr); | |||
| } | |||
| deviceIsOpen = false; | |||
| } | |||
| void start (AudioIODeviceCallback* newCallback) override | |||
| { | |||
| if (deviceIsOpen && newCallback != callback) | |||
| { | |||
| if (newCallback != nullptr) | |||
| newCallback->audioDeviceAboutToStart (this); | |||
| AudioIODeviceCallback* const oldCallback = callback; | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| callback = newCallback; | |||
| } | |||
| if (oldCallback != nullptr) | |||
| oldCallback->audioDeviceStopped(); | |||
| } | |||
| } | |||
| void stop() override | |||
| { | |||
| start (nullptr); | |||
| } | |||
| bool isOpen() override { return deviceIsOpen; } | |||
| bool isPlaying() override { return callback != nullptr; } | |||
| int getCurrentBitDepth() override { return 32; } | |||
| String getLastError() override { return lastError; } | |||
| BigInteger getActiveOutputChannels() const override { return activeOutputChannels; } | |||
| BigInteger getActiveInputChannels() const override { return activeInputChannels; } | |||
| int getOutputLatencyInSamples() override | |||
| { | |||
| int latency = 0; | |||
| for (int i = 0; i < outputPorts.size(); i++) | |||
| latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, (jack_port_t*) outputPorts [i])); | |||
| return latency; | |||
| } | |||
| int getInputLatencyInSamples() override | |||
| { | |||
| int latency = 0; | |||
| for (int i = 0; i < inputPorts.size(); i++) | |||
| latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, (jack_port_t*) inputPorts [i])); | |||
| return latency; | |||
| } | |||
| String inputId, outputId; | |||
| private: | |||
| void process (const int numSamples) | |||
| { | |||
| int numActiveInChans = 0, numActiveOutChans = 0; | |||
| for (int i = 0; i < totalNumberOfInputChannels; ++i) | |||
| { | |||
| if (activeInputChannels[i]) | |||
| if (jack_default_audio_sample_t* in | |||
| = (jack_default_audio_sample_t*) juce::jack_port_get_buffer ((jack_port_t*) inputPorts.getUnchecked(i), numSamples)) | |||
| inChans [numActiveInChans++] = (float*) in; | |||
| } | |||
| for (int i = 0; i < totalNumberOfOutputChannels; ++i) | |||
| { | |||
| if (activeOutputChannels[i]) | |||
| if (jack_default_audio_sample_t* out | |||
| = (jack_default_audio_sample_t*) juce::jack_port_get_buffer ((jack_port_t*) outputPorts.getUnchecked(i), numSamples)) | |||
| outChans [numActiveOutChans++] = (float*) out; | |||
| } | |||
| const ScopedLock sl (callbackLock); | |||
| if (callback != nullptr) | |||
| { | |||
| if ((numActiveInChans + numActiveOutChans) > 0) | |||
| callback->audioDeviceIOCallback (const_cast <const float**> (inChans.getData()), numActiveInChans, | |||
| outChans, numActiveOutChans, numSamples); | |||
| } | |||
| else | |||
| { | |||
| for (int i = 0; i < numActiveOutChans; ++i) | |||
| zeromem (outChans[i], sizeof (float) * numSamples); | |||
| } | |||
| } | |||
| static int processCallback (jack_nframes_t nframes, void* callbackArgument) | |||
| { | |||
| if (callbackArgument != nullptr) | |||
| ((JackAudioIODevice*) callbackArgument)->process (nframes); | |||
| return 0; | |||
| } | |||
| void updateActivePorts() | |||
| { | |||
| BigInteger newOutputChannels, newInputChannels; | |||
| for (int i = 0; i < outputPorts.size(); ++i) | |||
| if (juce::jack_port_connected ((jack_port_t*) outputPorts.getUnchecked(i))) | |||
| newOutputChannels.setBit (i); | |||
| for (int i = 0; i < inputPorts.size(); ++i) | |||
| if (juce::jack_port_connected ((jack_port_t*) inputPorts.getUnchecked(i))) | |||
| newInputChannels.setBit (i); | |||
| if (newOutputChannels != activeOutputChannels | |||
| || newInputChannels != activeInputChannels) | |||
| { | |||
| AudioIODeviceCallback* const oldCallback = callback; | |||
| stop(); | |||
| activeOutputChannels = newOutputChannels; | |||
| activeInputChannels = newInputChannels; | |||
| if (oldCallback != nullptr) | |||
| start (oldCallback); | |||
| sendDeviceChangedCallback(); | |||
| } | |||
| } | |||
| static void portConnectCallback (jack_port_id_t, jack_port_id_t, int, void* arg) | |||
| { | |||
| if (JackAudioIODevice* device = static_cast <JackAudioIODevice*> (arg)) | |||
| device->updateActivePorts(); | |||
| } | |||
| static void threadInitCallback (void* /* callbackArgument */) | |||
| { | |||
| JUCE_JACK_LOG ("JackAudioIODevice::initialise"); | |||
| } | |||
| static void shutdownCallback (void* callbackArgument) | |||
| { | |||
| JUCE_JACK_LOG ("JackAudioIODevice::shutdown"); | |||
| if (JackAudioIODevice* device = (JackAudioIODevice*) callbackArgument) | |||
| { | |||
| device->client = nullptr; | |||
| device->close(); | |||
| } | |||
| } | |||
| static void errorCallback (const char* msg) | |||
| { | |||
| JUCE_JACK_LOG ("JackAudioIODevice::errorCallback " + String (msg)); | |||
| } | |||
| static void sendDeviceChangedCallback(); | |||
| bool deviceIsOpen; | |||
| jack_client_t* client; | |||
| String lastError; | |||
| AudioIODeviceCallback* callback; | |||
| CriticalSection callbackLock; | |||
| HeapBlock <float*> inChans, outChans; | |||
| int totalNumberOfInputChannels; | |||
| int totalNumberOfOutputChannels; | |||
| Array<void*> inputPorts, outputPorts; | |||
| BigInteger activeInputChannels, activeOutputChannels; | |||
| }; | |||
| //============================================================================== | |||
| class JackAudioIODeviceType : public AudioIODeviceType | |||
| { | |||
| public: | |||
| JackAudioIODeviceType() | |||
| : AudioIODeviceType ("JACK"), | |||
| hasScanned (false) | |||
| { | |||
| activeDeviceTypes.add (this); | |||
| } | |||
| ~JackAudioIODeviceType() | |||
| { | |||
| activeDeviceTypes.removeFirstMatchingValue (this); | |||
| } | |||
| void scanForDevices() | |||
| { | |||
| hasScanned = true; | |||
| inputNames.clear(); | |||
| inputIds.clear(); | |||
| outputNames.clear(); | |||
| outputIds.clear(); | |||
| if (juce_libjackHandle == nullptr) juce_libjackHandle = dlopen ("libjack.so.0", RTLD_LAZY); | |||
| if (juce_libjackHandle == nullptr) juce_libjackHandle = dlopen ("libjack.so", RTLD_LAZY); | |||
| if (juce_libjackHandle == nullptr) return; | |||
| jack_status_t status; | |||
| // open a dummy client | |||
| if (jack_client_t* const client = juce::jack_client_open ("JuceJackDummy", JackNoStartServer, &status)) | |||
| { | |||
| // scan for output devices | |||
| for (JackPortIterator i (client, false); i.next();) | |||
| { | |||
| if (i.clientName != (JUCE_JACK_CLIENT_NAME) && ! inputNames.contains (i.clientName)) | |||
| { | |||
| inputNames.add (i.clientName); | |||
| inputIds.add (i.ports [i.index]); | |||
| } | |||
| } | |||
| // scan for input devices | |||
| for (JackPortIterator i (client, true); i.next();) | |||
| { | |||
| if (i.clientName != (JUCE_JACK_CLIENT_NAME) && ! outputNames.contains (i.clientName)) | |||
| { | |||
| outputNames.add (i.clientName); | |||
| outputIds.add (i.ports [i.index]); | |||
| } | |||
| } | |||
| juce::jack_client_close (client); | |||
| } | |||
| else | |||
| { | |||
| JUCE_JACK_LOG_STATUS (status); | |||
| } | |||
| } | |||
| StringArray getDeviceNames (bool wantInputNames) const | |||
| { | |||
| jassert (hasScanned); // need to call scanForDevices() before doing this | |||
| return wantInputNames ? inputNames : outputNames; | |||
| } | |||
| int getDefaultDeviceIndex (bool /* forInput */) const | |||
| { | |||
| jassert (hasScanned); // need to call scanForDevices() before doing this | |||
| return 0; | |||
| } | |||
| bool hasSeparateInputsAndOutputs() const { return true; } | |||
| int getIndexOfDevice (AudioIODevice* device, bool asInput) const | |||
| { | |||
| jassert (hasScanned); // need to call scanForDevices() before doing this | |||
| if (JackAudioIODevice* d = dynamic_cast <JackAudioIODevice*> (device)) | |||
| return asInput ? inputIds.indexOf (d->inputId) | |||
| : outputIds.indexOf (d->outputId); | |||
| return -1; | |||
| } | |||
| AudioIODevice* createDevice (const String& outputDeviceName, | |||
| const String& inputDeviceName) | |||
| { | |||
| jassert (hasScanned); // need to call scanForDevices() before doing this | |||
| const int inputIndex = inputNames.indexOf (inputDeviceName); | |||
| const int outputIndex = outputNames.indexOf (outputDeviceName); | |||
| if (inputIndex >= 0 || outputIndex >= 0) | |||
| return new JackAudioIODevice (outputIndex >= 0 ? outputDeviceName | |||
| : inputDeviceName, | |||
| inputIds [inputIndex], | |||
| outputIds [outputIndex]); | |||
| return nullptr; | |||
| } | |||
| void portConnectionChange() { callDeviceChangeListeners(); } | |||
| private: | |||
| StringArray inputNames, outputNames, inputIds, outputIds; | |||
| bool hasScanned; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JackAudioIODeviceType) | |||
| }; | |||
| void JackAudioIODevice::sendDeviceChangedCallback() | |||
| { | |||
| for (int i = activeDeviceTypes.size(); --i >= 0;) | |||
| if (JackAudioIODeviceType* d = activeDeviceTypes[i]) | |||
| d->portConnectionChange(); | |||
| } | |||
| //============================================================================== | |||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK() | |||
| { | |||
| return new JackAudioIODeviceType(); | |||
| } | |||
| @@ -1,612 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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_ALSA | |||
| // You can define these strings in your app if you want to override the default names: | |||
| #ifndef JUCE_ALSA_MIDI_INPUT_NAME | |||
| #define JUCE_ALSA_MIDI_INPUT_NAME "Juce Midi Input" | |||
| #endif | |||
| #ifndef JUCE_ALSA_MIDI_OUTPUT_NAME | |||
| #define JUCE_ALSA_MIDI_OUTPUT_NAME "Juce Midi Output" | |||
| #endif | |||
| //============================================================================== | |||
| namespace | |||
| { | |||
| class AlsaPortAndCallback; | |||
| //============================================================================== | |||
| class AlsaClient : public ReferenceCountedObject | |||
| { | |||
| public: | |||
| typedef ReferenceCountedObjectPtr<AlsaClient> Ptr; | |||
| AlsaClient (bool forInput) | |||
| : input (forInput), handle (nullptr) | |||
| { | |||
| snd_seq_open (&handle, "default", forInput ? SND_SEQ_OPEN_INPUT | |||
| : SND_SEQ_OPEN_OUTPUT, 0); | |||
| } | |||
| ~AlsaClient() | |||
| { | |||
| if (handle != nullptr) | |||
| { | |||
| snd_seq_close (handle); | |||
| handle = nullptr; | |||
| } | |||
| jassert (activeCallbacks.size() == 0); | |||
| if (inputThread) | |||
| { | |||
| inputThread->stopThread (3000); | |||
| inputThread = nullptr; | |||
| } | |||
| } | |||
| bool isInput() const noexcept { return input; } | |||
| void setName (const String& name) | |||
| { | |||
| snd_seq_set_client_name (handle, name.toUTF8()); | |||
| } | |||
| void registerCallback (AlsaPortAndCallback* cb) | |||
| { | |||
| if (cb != nullptr) | |||
| { | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| activeCallbacks.add (cb); | |||
| if (inputThread == nullptr) | |||
| inputThread = new MidiInputThread (*this); | |||
| } | |||
| inputThread->startThread(); | |||
| } | |||
| } | |||
| void unregisterCallback (AlsaPortAndCallback* cb) | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| jassert (activeCallbacks.contains (cb)); | |||
| activeCallbacks.removeAllInstancesOf (cb); | |||
| if (activeCallbacks.size() == 0 && inputThread->isThreadRunning()) | |||
| inputThread->signalThreadShouldExit(); | |||
| } | |||
| void handleIncomingMidiMessage (const MidiMessage& message, int port); | |||
| snd_seq_t* get() const noexcept { return handle; } | |||
| private: | |||
| bool input; | |||
| snd_seq_t* handle; | |||
| Array<AlsaPortAndCallback*> activeCallbacks; | |||
| CriticalSection callbackLock; | |||
| //============================================================================== | |||
| class MidiInputThread : public Thread | |||
| { | |||
| public: | |||
| MidiInputThread (AlsaClient& c) | |||
| : Thread ("Juce MIDI Input"), client (c) | |||
| { | |||
| jassert (client.input && client.get() != nullptr); | |||
| } | |||
| void run() override | |||
| { | |||
| const int maxEventSize = 16 * 1024; | |||
| snd_midi_event_t* midiParser; | |||
| snd_seq_t* seqHandle = client.get(); | |||
| if (snd_midi_event_new (maxEventSize, &midiParser) >= 0) | |||
| { | |||
| const int numPfds = snd_seq_poll_descriptors_count (seqHandle, POLLIN); | |||
| HeapBlock<pollfd> pfd (numPfds); | |||
| snd_seq_poll_descriptors (seqHandle, pfd, numPfds, POLLIN); | |||
| HeapBlock <uint8> buffer (maxEventSize); | |||
| while (! threadShouldExit()) | |||
| { | |||
| if (poll (pfd, numPfds, 100) > 0) // there was a "500" here which is a bit long when we exit the program and have to wait for a timeout on this poll call | |||
| { | |||
| if (threadShouldExit()) | |||
| break; | |||
| snd_seq_nonblock (seqHandle, 1); | |||
| do | |||
| { | |||
| snd_seq_event_t* inputEvent = nullptr; | |||
| if (snd_seq_event_input (seqHandle, &inputEvent) >= 0) | |||
| { | |||
| // xxx what about SYSEXes that are too big for the buffer? | |||
| const int numBytes = snd_midi_event_decode (midiParser, buffer, | |||
| maxEventSize, inputEvent); | |||
| snd_midi_event_reset_decode (midiParser); | |||
| if (numBytes > 0) | |||
| { | |||
| const MidiMessage message ((const uint8*) buffer, numBytes, | |||
| Time::getMillisecondCounter() * 0.001); | |||
| client.handleIncomingMidiMessage (message, inputEvent->dest.port); | |||
| } | |||
| snd_seq_free_event (inputEvent); | |||
| } | |||
| } | |||
| while (snd_seq_event_input_pending (seqHandle, 0) > 0); | |||
| } | |||
| } | |||
| snd_midi_event_free (midiParser); | |||
| } | |||
| }; | |||
| private: | |||
| AlsaClient& client; | |||
| }; | |||
| ScopedPointer<MidiInputThread> inputThread; | |||
| }; | |||
| static AlsaClient::Ptr globalAlsaSequencerIn() | |||
| { | |||
| static AlsaClient::Ptr global (new AlsaClient (true)); | |||
| return global; | |||
| } | |||
| static AlsaClient::Ptr globalAlsaSequencerOut() | |||
| { | |||
| static AlsaClient::Ptr global (new AlsaClient (false)); | |||
| return global; | |||
| } | |||
| static AlsaClient::Ptr globalAlsaSequencer (bool input) | |||
| { | |||
| return input ? globalAlsaSequencerIn() | |||
| : globalAlsaSequencerOut(); | |||
| } | |||
| //============================================================================== | |||
| // represents an input or output port of the supplied AlsaClient | |||
| class AlsaPort | |||
| { | |||
| public: | |||
| AlsaPort() noexcept : portId (-1) {} | |||
| AlsaPort (const AlsaClient::Ptr& c, int port) noexcept : client (c), portId (port) {} | |||
| void createPort (const AlsaClient::Ptr& c, const String& name, bool forInput) | |||
| { | |||
| client = c; | |||
| if (snd_seq_t* handle = client->get()) | |||
| portId = snd_seq_create_simple_port (handle, name.toUTF8(), | |||
| forInput ? (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE) | |||
| : (SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ), | |||
| SND_SEQ_PORT_TYPE_MIDI_GENERIC); | |||
| } | |||
| void deletePort() | |||
| { | |||
| if (isValid()) | |||
| { | |||
| snd_seq_delete_simple_port (client->get(), portId); | |||
| portId = -1; | |||
| } | |||
| } | |||
| void connectWith (int sourceClient, int sourcePort) | |||
| { | |||
| if (client->isInput()) | |||
| snd_seq_connect_from (client->get(), portId, sourceClient, sourcePort); | |||
| else | |||
| snd_seq_connect_to (client->get(), portId, sourceClient, sourcePort); | |||
| } | |||
| bool isValid() const noexcept | |||
| { | |||
| return client != nullptr && client->get() != nullptr && portId >= 0; | |||
| } | |||
| AlsaClient::Ptr client; | |||
| int portId; | |||
| }; | |||
| //============================================================================== | |||
| class AlsaPortAndCallback | |||
| { | |||
| public: | |||
| AlsaPortAndCallback (AlsaPort p, MidiInput* in, MidiInputCallback* cb) | |||
| : port (p), midiInput (in), callback (cb), callbackEnabled (false) | |||
| { | |||
| } | |||
| ~AlsaPortAndCallback() | |||
| { | |||
| enableCallback (false); | |||
| port.deletePort(); | |||
| } | |||
| void enableCallback (bool enable) | |||
| { | |||
| if (callbackEnabled != enable) | |||
| { | |||
| callbackEnabled = enable; | |||
| if (enable) | |||
| port.client->registerCallback (this); | |||
| else | |||
| port.client->unregisterCallback (this); | |||
| } | |||
| } | |||
| void handleIncomingMidiMessage (const MidiMessage& message) const | |||
| { | |||
| callback->handleIncomingMidiMessage (midiInput, message); | |||
| } | |||
| private: | |||
| AlsaPort port; | |||
| MidiInput* midiInput; | |||
| MidiInputCallback* callback; | |||
| bool callbackEnabled; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AlsaPortAndCallback) | |||
| }; | |||
| void AlsaClient::handleIncomingMidiMessage (const MidiMessage& message, int port) | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| if (AlsaPortAndCallback* const cb = activeCallbacks[port]) | |||
| cb->handleIncomingMidiMessage (message); | |||
| } | |||
| //============================================================================== | |||
| static AlsaPort iterateMidiClient (const AlsaClient::Ptr& seq, | |||
| snd_seq_client_info_t* clientInfo, | |||
| const bool forInput, | |||
| StringArray& deviceNamesFound, | |||
| const int deviceIndexToOpen) | |||
| { | |||
| AlsaPort port; | |||
| snd_seq_t* seqHandle = seq->get(); | |||
| snd_seq_port_info_t* portInfo = nullptr; | |||
| if (snd_seq_port_info_malloc (&portInfo) == 0) | |||
| { | |||
| int numPorts = snd_seq_client_info_get_num_ports (clientInfo); | |||
| const int client = snd_seq_client_info_get_client (clientInfo); | |||
| snd_seq_port_info_set_client (portInfo, client); | |||
| snd_seq_port_info_set_port (portInfo, -1); | |||
| while (--numPorts >= 0) | |||
| { | |||
| if (snd_seq_query_next_port (seqHandle, portInfo) == 0 | |||
| && (snd_seq_port_info_get_capability (portInfo) & (forInput ? SND_SEQ_PORT_CAP_READ | |||
| : SND_SEQ_PORT_CAP_WRITE)) != 0) | |||
| { | |||
| deviceNamesFound.add (snd_seq_client_info_get_name (clientInfo)); | |||
| if (deviceNamesFound.size() == deviceIndexToOpen + 1) | |||
| { | |||
| const int sourcePort = snd_seq_port_info_get_port (portInfo); | |||
| const int sourceClient = snd_seq_client_info_get_client (clientInfo); | |||
| if (sourcePort != -1) | |||
| { | |||
| const String name (forInput ? JUCE_ALSA_MIDI_INPUT_NAME | |||
| : JUCE_ALSA_MIDI_OUTPUT_NAME); | |||
| seq->setName (name); | |||
| port.createPort (seq, name, forInput); | |||
| port.connectWith (sourceClient, sourcePort); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| snd_seq_port_info_free (portInfo); | |||
| } | |||
| return port; | |||
| } | |||
| static AlsaPort iterateMidiDevices (const bool forInput, | |||
| StringArray& deviceNamesFound, | |||
| const int deviceIndexToOpen) | |||
| { | |||
| AlsaPort port; | |||
| const AlsaClient::Ptr client (globalAlsaSequencer (forInput)); | |||
| if (snd_seq_t* const seqHandle = client->get()) | |||
| { | |||
| snd_seq_system_info_t* systemInfo = nullptr; | |||
| snd_seq_client_info_t* clientInfo = nullptr; | |||
| if (snd_seq_system_info_malloc (&systemInfo) == 0) | |||
| { | |||
| if (snd_seq_system_info (seqHandle, systemInfo) == 0 | |||
| && snd_seq_client_info_malloc (&clientInfo) == 0) | |||
| { | |||
| int numClients = snd_seq_system_info_get_cur_clients (systemInfo); | |||
| while (--numClients >= 0 && ! port.isValid()) | |||
| if (snd_seq_query_next_client (seqHandle, clientInfo) == 0) | |||
| port = iterateMidiClient (client, clientInfo, forInput, | |||
| deviceNamesFound, deviceIndexToOpen); | |||
| snd_seq_client_info_free (clientInfo); | |||
| } | |||
| snd_seq_system_info_free (systemInfo); | |||
| } | |||
| } | |||
| deviceNamesFound.appendNumbersToDuplicates (true, true); | |||
| return port; | |||
| } | |||
| AlsaPort createMidiDevice (const bool forInput, const String& deviceNameToOpen) | |||
| { | |||
| AlsaPort port; | |||
| AlsaClient::Ptr client (new AlsaClient (forInput)); | |||
| if (client->get()) | |||
| { | |||
| client->setName (deviceNameToOpen + (forInput ? " Input" : " Output")); | |||
| port.createPort (client, forInput ? "in" : "out", forInput); | |||
| } | |||
| return port; | |||
| } | |||
| //============================================================================== | |||
| class MidiOutputDevice | |||
| { | |||
| public: | |||
| MidiOutputDevice (MidiOutput* const output, const AlsaPort& p) | |||
| : midiOutput (output), port (p), | |||
| maxEventSize (16 * 1024) | |||
| { | |||
| jassert (port.isValid() && midiOutput != nullptr); | |||
| snd_midi_event_new (maxEventSize, &midiParser); | |||
| } | |||
| ~MidiOutputDevice() | |||
| { | |||
| snd_midi_event_free (midiParser); | |||
| port.deletePort(); | |||
| } | |||
| void sendMessageNow (const MidiMessage& message) | |||
| { | |||
| if (message.getRawDataSize() > maxEventSize) | |||
| { | |||
| maxEventSize = message.getRawDataSize(); | |||
| snd_midi_event_free (midiParser); | |||
| snd_midi_event_new (maxEventSize, &midiParser); | |||
| } | |||
| snd_seq_event_t event; | |||
| snd_seq_ev_clear (&event); | |||
| long numBytes = (long) message.getRawDataSize(); | |||
| const uint8* data = message.getRawData(); | |||
| snd_seq_t* seqHandle = port.client->get(); | |||
| while (numBytes > 0) | |||
| { | |||
| const long numSent = snd_midi_event_encode (midiParser, data, numBytes, &event); | |||
| if (numSent <= 0) | |||
| break; | |||
| numBytes -= numSent; | |||
| data += numSent; | |||
| snd_seq_ev_set_source (&event, 0); | |||
| snd_seq_ev_set_subs (&event); | |||
| snd_seq_ev_set_direct (&event); | |||
| snd_seq_event_output (seqHandle, &event); | |||
| } | |||
| snd_seq_drain_output (seqHandle); | |||
| snd_midi_event_reset_encode (midiParser); | |||
| } | |||
| private: | |||
| MidiOutput* const midiOutput; | |||
| AlsaPort port; | |||
| snd_midi_event_t* midiParser; | |||
| int maxEventSize; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutputDevice); | |||
| }; | |||
| } // namespace | |||
| StringArray MidiOutput::getDevices() | |||
| { | |||
| StringArray devices; | |||
| iterateMidiDevices (false, devices, -1); | |||
| return devices; | |||
| } | |||
| int MidiOutput::getDefaultDeviceIndex() | |||
| { | |||
| return 0; | |||
| } | |||
| MidiOutput* MidiOutput::openDevice (int deviceIndex) | |||
| { | |||
| MidiOutput* newDevice = nullptr; | |||
| StringArray devices; | |||
| AlsaPort port (iterateMidiDevices (false, devices, deviceIndex)); | |||
| if (port.isValid()) | |||
| { | |||
| newDevice = new MidiOutput(); | |||
| newDevice->internal = new MidiOutputDevice (newDevice, port); | |||
| } | |||
| return newDevice; | |||
| } | |||
| MidiOutput* MidiOutput::createNewDevice (const String& deviceName) | |||
| { | |||
| MidiOutput* newDevice = nullptr; | |||
| AlsaPort port (createMidiDevice (false, deviceName)); | |||
| if (port.isValid()) | |||
| { | |||
| newDevice = new MidiOutput(); | |||
| newDevice->internal = new MidiOutputDevice (newDevice, port); | |||
| } | |||
| return newDevice; | |||
| } | |||
| MidiOutput::~MidiOutput() | |||
| { | |||
| stopBackgroundThread(); | |||
| delete static_cast<MidiOutputDevice*> (internal); | |||
| } | |||
| void MidiOutput::sendMessageNow (const MidiMessage& message) | |||
| { | |||
| static_cast<MidiOutputDevice*> (internal)->sendMessageNow (message); | |||
| } | |||
| //============================================================================== | |||
| MidiInput::MidiInput (const String& nm) | |||
| : name (nm), internal (nullptr) | |||
| { | |||
| } | |||
| MidiInput::~MidiInput() | |||
| { | |||
| stop(); | |||
| delete static_cast<AlsaPortAndCallback*> (internal); | |||
| } | |||
| void MidiInput::start() | |||
| { | |||
| static_cast<AlsaPortAndCallback*> (internal)->enableCallback (true); | |||
| } | |||
| void MidiInput::stop() | |||
| { | |||
| static_cast<AlsaPortAndCallback*> (internal)->enableCallback (false); | |||
| } | |||
| int MidiInput::getDefaultDeviceIndex() | |||
| { | |||
| return 0; | |||
| } | |||
| StringArray MidiInput::getDevices() | |||
| { | |||
| StringArray devices; | |||
| iterateMidiDevices (true, devices, -1); | |||
| return devices; | |||
| } | |||
| MidiInput* MidiInput::openDevice (int deviceIndex, MidiInputCallback* callback) | |||
| { | |||
| MidiInput* newDevice = nullptr; | |||
| StringArray devices; | |||
| AlsaPort port (iterateMidiDevices (true, devices, deviceIndex)); | |||
| if (port.isValid()) | |||
| { | |||
| newDevice = new MidiInput (devices [deviceIndex]); | |||
| newDevice->internal = new AlsaPortAndCallback (port, newDevice, callback); | |||
| } | |||
| return newDevice; | |||
| } | |||
| MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) | |||
| { | |||
| MidiInput* newDevice = nullptr; | |||
| AlsaPort port (createMidiDevice (true, deviceName)); | |||
| if (port.isValid()) | |||
| { | |||
| newDevice = new MidiInput (deviceName); | |||
| newDevice->internal = new AlsaPortAndCallback (port, newDevice, callback); | |||
| } | |||
| return newDevice; | |||
| } | |||
| //============================================================================== | |||
| #else | |||
| // (These are just stub functions if ALSA is unavailable...) | |||
| StringArray MidiOutput::getDevices() { return StringArray(); } | |||
| int MidiOutput::getDefaultDeviceIndex() { return 0; } | |||
| MidiOutput* MidiOutput::openDevice (int) { return nullptr; } | |||
| MidiOutput* MidiOutput::createNewDevice (const String&) { return nullptr; } | |||
| MidiOutput::~MidiOutput() {} | |||
| void MidiOutput::sendMessageNow (const MidiMessage&) {} | |||
| MidiInput::MidiInput (const String& nm) : name (nm), internal (nullptr) {} | |||
| MidiInput::~MidiInput() {} | |||
| void MidiInput::start() {} | |||
| void MidiInput::stop() {} | |||
| int MidiInput::getDefaultDeviceIndex() { return 0; } | |||
| StringArray MidiInput::getDevices() { return StringArray(); } | |||
| MidiInput* MidiInput::openDevice (int, MidiInputCallback*) { return nullptr; } | |||
| MidiInput* MidiInput::createNewDevice (const String&, MidiInputCallback*) { return nullptr; } | |||
| #endif | |||
| @@ -1,455 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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. | |||
| ============================================================================== | |||
| */ | |||
| const int kilobytesPerSecond1x = 176; | |||
| struct AudioTrackProducerClass : public ObjCClass <NSObject> | |||
| { | |||
| AudioTrackProducerClass() : ObjCClass <NSObject> ("JUCEAudioTrackProducer_") | |||
| { | |||
| addIvar<AudioSourceHolder*> ("source"); | |||
| addMethod (@selector (initWithAudioSourceHolder:), initWithAudioSourceHolder, "@@:^v"); | |||
| addMethod (@selector (cleanupTrackAfterBurn:), cleanupTrackAfterBurn, "v@:@"); | |||
| addMethod (@selector (cleanupTrackAfterVerification:), cleanupTrackAfterVerification, "c@:@"); | |||
| addMethod (@selector (estimateLengthOfTrack:), estimateLengthOfTrack, "Q@:@"); | |||
| addMethod (@selector (prepareTrack:forBurn:toMedia:), prepareTrack, "c@:@@@"); | |||
| addMethod (@selector (prepareTrackForVerification:), prepareTrackForVerification, "c@:@"); | |||
| addMethod (@selector (produceDataForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:), | |||
| produceDataForTrack, "I@:@^cIQI^I"); | |||
| addMethod (@selector (producePreGapForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:), | |||
| produceDataForTrack, "I@:@^cIQI^I"); | |||
| addMethod (@selector (verifyDataForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:), | |||
| produceDataForTrack, "I@:@^cIQI^I"); | |||
| registerClass(); | |||
| } | |||
| struct AudioSourceHolder | |||
| { | |||
| AudioSourceHolder (AudioSource* s, int numFrames) | |||
| : source (s), readPosition (0), lengthInFrames (numFrames) | |||
| { | |||
| } | |||
| ~AudioSourceHolder() | |||
| { | |||
| if (source != nullptr) | |||
| source->releaseResources(); | |||
| } | |||
| ScopedPointer<AudioSource> source; | |||
| int readPosition, lengthInFrames; | |||
| }; | |||
| private: | |||
| static id initWithAudioSourceHolder (id self, SEL, AudioSourceHolder* source) | |||
| { | |||
| self = sendSuperclassMessage (self, @selector (init)); | |||
| object_setInstanceVariable (self, "source", source); | |||
| return self; | |||
| } | |||
| static AudioSourceHolder* getSource (id self) | |||
| { | |||
| return getIvar<AudioSourceHolder*> (self, "source"); | |||
| } | |||
| static void dealloc (id self, SEL) | |||
| { | |||
| delete getSource (self); | |||
| sendSuperclassMessage (self, @selector (dealloc)); | |||
| } | |||
| static void cleanupTrackAfterBurn (id self, SEL, DRTrack*) {} | |||
| static BOOL cleanupTrackAfterVerification (id self, SEL, DRTrack*) { return true; } | |||
| static uint64_t estimateLengthOfTrack (id self, SEL, DRTrack*) | |||
| { | |||
| return getSource (self)->lengthInFrames; | |||
| } | |||
| static BOOL prepareTrack (id self, SEL, DRTrack*, DRBurn*, NSDictionary*) | |||
| { | |||
| if (AudioSourceHolder* const source = getSource (self)) | |||
| { | |||
| source->source->prepareToPlay (44100 / 75, 44100); | |||
| source->readPosition = 0; | |||
| } | |||
| return true; | |||
| } | |||
| static BOOL prepareTrackForVerification (id self, SEL, DRTrack*) | |||
| { | |||
| if (AudioSourceHolder* const source = getSource (self)) | |||
| source->source->prepareToPlay (44100 / 75, 44100); | |||
| return true; | |||
| } | |||
| static uint32_t produceDataForTrack (id self, SEL, DRTrack*, char* buffer, | |||
| uint32_t bufferLength, uint64_t /*address*/, | |||
| uint32_t /*blockSize*/, uint32_t* /*flags*/) | |||
| { | |||
| if (AudioSourceHolder* const source = getSource (self)) | |||
| { | |||
| const int numSamples = jmin ((int) bufferLength / 4, | |||
| (source->lengthInFrames * (44100 / 75)) - source->readPosition); | |||
| if (numSamples > 0) | |||
| { | |||
| AudioSampleBuffer tempBuffer (2, numSamples); | |||
| AudioSourceChannelInfo info (tempBuffer); | |||
| source->source->getNextAudioBlock (info); | |||
| typedef AudioData::Pointer <AudioData::Int16, AudioData::LittleEndian, AudioData::Interleaved, AudioData::NonConst> CDSampleFormat; | |||
| typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const> SourceSampleFormat; | |||
| CDSampleFormat left (buffer, 2); | |||
| left.convertSamples (SourceSampleFormat (tempBuffer.getReadPointer (0)), numSamples); | |||
| CDSampleFormat right (buffer + 2, 2); | |||
| right.convertSamples (SourceSampleFormat (tempBuffer.getReadPointer (1)), numSamples); | |||
| source->readPosition += numSamples; | |||
| } | |||
| return numSamples * 4; | |||
| } | |||
| return 0; | |||
| } | |||
| static uint32_t producePreGapForTrack (id self, SEL, DRTrack*, char* buffer, | |||
| uint32_t bufferLength, uint64_t /*address*/, | |||
| uint32_t /*blockSize*/, uint32_t* /*flags*/) | |||
| { | |||
| zeromem (buffer, bufferLength); | |||
| return bufferLength; | |||
| } | |||
| static BOOL verifyDataForTrack (id self, SEL, DRTrack*, const char*, | |||
| uint32_t /*bufferLength*/, uint64_t /*address*/, | |||
| uint32_t /*blockSize*/, uint32_t* /*flags*/) | |||
| { | |||
| return true; | |||
| } | |||
| }; | |||
| struct OpenDiskDevice | |||
| { | |||
| OpenDiskDevice (DRDevice* d) | |||
| : device (d), | |||
| tracks ([[NSMutableArray alloc] init]), | |||
| underrunProtection (true) | |||
| { | |||
| } | |||
| ~OpenDiskDevice() | |||
| { | |||
| [tracks release]; | |||
| } | |||
| void addSourceTrack (AudioSource* source, int numSamples) | |||
| { | |||
| if (source != nullptr) | |||
| { | |||
| const int numFrames = (numSamples + 587) / 588; | |||
| static AudioTrackProducerClass cls; | |||
| NSObject* producer = [cls.createInstance() performSelector: @selector (initWithAudioSourceHolder:) | |||
| withObject: (id) new AudioTrackProducerClass::AudioSourceHolder (source, numFrames)]; | |||
| DRTrack* track = [[DRTrack alloc] initWithProducer: producer]; | |||
| { | |||
| NSMutableDictionary* p = [[track properties] mutableCopy]; | |||
| [p setObject: [DRMSF msfWithFrames: numFrames] forKey: DRTrackLengthKey]; | |||
| [p setObject: [NSNumber numberWithUnsignedShort: 2352] forKey: DRBlockSizeKey]; | |||
| [p setObject: [NSNumber numberWithInt: 0] forKey: DRDataFormKey]; | |||
| [p setObject: [NSNumber numberWithInt: 0] forKey: DRBlockTypeKey]; | |||
| [p setObject: [NSNumber numberWithInt: 0] forKey: DRTrackModeKey]; | |||
| [p setObject: [NSNumber numberWithInt: 0] forKey: DRSessionFormatKey]; | |||
| [track setProperties: p]; | |||
| [p release]; | |||
| } | |||
| [tracks addObject: track]; | |||
| [track release]; | |||
| [producer release]; | |||
| } | |||
| } | |||
| String burn (AudioCDBurner::BurnProgressListener* listener, | |||
| bool shouldEject, bool peformFakeBurnForTesting, int burnSpeed) | |||
| { | |||
| DRBurn* burn = [DRBurn burnForDevice: device]; | |||
| if (! [device acquireExclusiveAccess]) | |||
| return "Couldn't open or write to the CD device"; | |||
| [device acquireMediaReservation]; | |||
| NSMutableDictionary* d = [[burn properties] mutableCopy]; | |||
| [d autorelease]; | |||
| [d setObject: [NSNumber numberWithBool: peformFakeBurnForTesting] forKey: DRBurnTestingKey]; | |||
| [d setObject: [NSNumber numberWithBool: false] forKey: DRBurnVerifyDiscKey]; | |||
| [d setObject: (shouldEject ? DRBurnCompletionActionEject : DRBurnCompletionActionMount) forKey: DRBurnCompletionActionKey]; | |||
| if (burnSpeed > 0) | |||
| [d setObject: [NSNumber numberWithFloat: burnSpeed * kilobytesPerSecond1x] forKey: DRBurnRequestedSpeedKey]; | |||
| if (! underrunProtection) | |||
| [d setObject: [NSNumber numberWithBool: false] forKey: DRBurnUnderrunProtectionKey]; | |||
| [burn setProperties: d]; | |||
| [burn writeLayout: tracks]; | |||
| for (;;) | |||
| { | |||
| Thread::sleep (300); | |||
| float progress = [[[burn status] objectForKey: DRStatusPercentCompleteKey] floatValue]; | |||
| if (listener != nullptr && listener->audioCDBurnProgress (progress)) | |||
| { | |||
| [burn abort]; | |||
| return "User cancelled the write operation"; | |||
| } | |||
| if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateFailed]) | |||
| return "Write operation failed"; | |||
| if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateDone]) | |||
| break; | |||
| NSString* err = (NSString*) [[[burn status] objectForKey: DRErrorStatusKey] | |||
| objectForKey: DRErrorStatusErrorStringKey]; | |||
| if ([err length] > 0) | |||
| return nsStringToJuce (err); | |||
| } | |||
| [device releaseMediaReservation]; | |||
| [device releaseExclusiveAccess]; | |||
| return String::empty; | |||
| } | |||
| DRDevice* device; | |||
| NSMutableArray* tracks; | |||
| bool underrunProtection; | |||
| }; | |||
| //============================================================================== | |||
| class AudioCDBurner::Pimpl : public Timer | |||
| { | |||
| public: | |||
| Pimpl (AudioCDBurner& b, int deviceIndex) : owner (b) | |||
| { | |||
| if (DRDevice* dev = [[DRDevice devices] objectAtIndex: deviceIndex]) | |||
| { | |||
| device = new OpenDiskDevice (dev); | |||
| lastState = getDiskState(); | |||
| startTimer (1000); | |||
| } | |||
| } | |||
| ~Pimpl() | |||
| { | |||
| stopTimer(); | |||
| } | |||
| void timerCallback() override | |||
| { | |||
| const DiskState state = getDiskState(); | |||
| if (state != lastState) | |||
| { | |||
| lastState = state; | |||
| owner.sendChangeMessage(); | |||
| } | |||
| } | |||
| DiskState getDiskState() const | |||
| { | |||
| if ([device->device isValid]) | |||
| { | |||
| NSDictionary* status = [device->device status]; | |||
| NSString* state = [status objectForKey: DRDeviceMediaStateKey]; | |||
| if ([state isEqualTo: DRDeviceMediaStateNone]) | |||
| { | |||
| if ([[status objectForKey: DRDeviceIsTrayOpenKey] boolValue]) | |||
| return trayOpen; | |||
| return noDisc; | |||
| } | |||
| if ([state isEqualTo: DRDeviceMediaStateMediaPresent]) | |||
| { | |||
| if ([[[status objectForKey: DRDeviceMediaInfoKey] objectForKey: DRDeviceMediaBlocksFreeKey] intValue] > 0) | |||
| return writableDiskPresent; | |||
| return readOnlyDiskPresent; | |||
| } | |||
| } | |||
| return unknown; | |||
| } | |||
| bool openTray() { return [device->device isValid] && [device->device ejectMedia]; } | |||
| Array<int> getAvailableWriteSpeeds() const | |||
| { | |||
| Array<int> results; | |||
| if ([device->device isValid]) | |||
| for (id kbPerSec in [[[device->device status] objectForKey: DRDeviceMediaInfoKey] objectForKey: DRDeviceBurnSpeedsKey]) | |||
| results.add ([kbPerSec intValue] / kilobytesPerSecond1x); | |||
| return results; | |||
| } | |||
| bool setBufferUnderrunProtection (const bool shouldBeEnabled) | |||
| { | |||
| if ([device->device isValid]) | |||
| { | |||
| device->underrunProtection = shouldBeEnabled; | |||
| return shouldBeEnabled && [[[device->device status] objectForKey: DRDeviceCanUnderrunProtectCDKey] boolValue]; | |||
| } | |||
| return false; | |||
| } | |||
| int getNumAvailableAudioBlocks() const | |||
| { | |||
| return [[[[device->device status] objectForKey: DRDeviceMediaInfoKey] | |||
| objectForKey: DRDeviceMediaBlocksFreeKey] intValue]; | |||
| } | |||
| ScopedPointer<OpenDiskDevice> device; | |||
| private: | |||
| DiskState lastState; | |||
| AudioCDBurner& owner; | |||
| }; | |||
| //============================================================================== | |||
| AudioCDBurner::AudioCDBurner (const int deviceIndex) | |||
| { | |||
| pimpl = new Pimpl (*this, deviceIndex); | |||
| } | |||
| AudioCDBurner::~AudioCDBurner() | |||
| { | |||
| } | |||
| AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex) | |||
| { | |||
| ScopedPointer<AudioCDBurner> b (new AudioCDBurner (deviceIndex)); | |||
| if (b->pimpl->device == nil) | |||
| b = nullptr; | |||
| return b.release(); | |||
| } | |||
| StringArray AudioCDBurner::findAvailableDevices() | |||
| { | |||
| StringArray s; | |||
| for (NSDictionary* dic in [DRDevice devices]) | |||
| if (NSString* name = [dic valueForKey: DRDeviceProductNameKey]) | |||
| s.add (nsStringToJuce (name)); | |||
| return s; | |||
| } | |||
| AudioCDBurner::DiskState AudioCDBurner::getDiskState() const | |||
| { | |||
| return pimpl->getDiskState(); | |||
| } | |||
| bool AudioCDBurner::isDiskPresent() const | |||
| { | |||
| return getDiskState() == writableDiskPresent; | |||
| } | |||
| bool AudioCDBurner::openTray() | |||
| { | |||
| return pimpl->openTray(); | |||
| } | |||
| AudioCDBurner::DiskState AudioCDBurner::waitUntilStateChange (int timeOutMilliseconds) | |||
| { | |||
| const int64 timeout = Time::currentTimeMillis() + timeOutMilliseconds; | |||
| DiskState oldState = getDiskState(); | |||
| DiskState newState = oldState; | |||
| while (newState == oldState && Time::currentTimeMillis() < timeout) | |||
| { | |||
| newState = getDiskState(); | |||
| Thread::sleep (100); | |||
| } | |||
| return newState; | |||
| } | |||
| Array<int> AudioCDBurner::getAvailableWriteSpeeds() const | |||
| { | |||
| return pimpl->getAvailableWriteSpeeds(); | |||
| } | |||
| bool AudioCDBurner::setBufferUnderrunProtection (const bool shouldBeEnabled) | |||
| { | |||
| return pimpl->setBufferUnderrunProtection (shouldBeEnabled); | |||
| } | |||
| int AudioCDBurner::getNumAvailableAudioBlocks() const | |||
| { | |||
| return pimpl->getNumAvailableAudioBlocks(); | |||
| } | |||
| bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamps) | |||
| { | |||
| if ([pimpl->device->device isValid]) | |||
| { | |||
| pimpl->device->addSourceTrack (source, numSamps); | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| String AudioCDBurner::burn (AudioCDBurner::BurnProgressListener* listener, | |||
| bool ejectDiscAfterwards, | |||
| bool performFakeBurnForTesting, | |||
| int writeSpeed) | |||
| { | |||
| if ([pimpl->device->device isValid]) | |||
| return pimpl->device->burn (listener, ejectDiscAfterwards, performFakeBurnForTesting, writeSpeed); | |||
| return "Couldn't open or write to the CD device"; | |||
| } | |||
| @@ -1,261 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 CDReaderHelpers | |||
| { | |||
| inline const XmlElement* getElementForKey (const XmlElement& xml, const String& key) | |||
| { | |||
| forEachXmlChildElementWithTagName (xml, child, "key") | |||
| if (child->getAllSubText().trim() == key) | |||
| return child->getNextElement(); | |||
| return nullptr; | |||
| } | |||
| static int getIntValueForKey (const XmlElement& xml, const String& key, int defaultValue = -1) | |||
| { | |||
| const XmlElement* const block = getElementForKey (xml, key); | |||
| return block != nullptr ? block->getAllSubText().trim().getIntValue() : defaultValue; | |||
| } | |||
| // Get the track offsets for a CD given an XmlElement representing its TOC.Plist. | |||
| // Returns NULL on success, otherwise a const char* representing an error. | |||
| static const char* getTrackOffsets (XmlDocument& xmlDocument, Array<int>& offsets) | |||
| { | |||
| const ScopedPointer<XmlElement> xml (xmlDocument.getDocumentElement()); | |||
| if (xml == nullptr) | |||
| return "Couldn't parse XML in file"; | |||
| const XmlElement* const dict = xml->getChildByName ("dict"); | |||
| if (dict == nullptr) | |||
| return "Couldn't get top level dictionary"; | |||
| const XmlElement* const sessions = getElementForKey (*dict, "Sessions"); | |||
| if (sessions == nullptr) | |||
| return "Couldn't find sessions key"; | |||
| const XmlElement* const session = sessions->getFirstChildElement(); | |||
| if (session == nullptr) | |||
| return "Couldn't find first session"; | |||
| const int leadOut = getIntValueForKey (*session, "Leadout Block"); | |||
| if (leadOut < 0) | |||
| return "Couldn't find Leadout Block"; | |||
| const XmlElement* const trackArray = getElementForKey (*session, "Track Array"); | |||
| if (trackArray == nullptr) | |||
| return "Couldn't find Track Array"; | |||
| forEachXmlChildElement (*trackArray, track) | |||
| { | |||
| const int trackValue = getIntValueForKey (*track, "Start Block"); | |||
| if (trackValue < 0) | |||
| return "Couldn't find Start Block in the track"; | |||
| offsets.add (trackValue * AudioCDReader::samplesPerFrame - 88200); | |||
| } | |||
| offsets.add (leadOut * AudioCDReader::samplesPerFrame - 88200); | |||
| return nullptr; | |||
| } | |||
| static void findDevices (Array<File>& cds) | |||
| { | |||
| File volumes ("/Volumes"); | |||
| volumes.findChildFiles (cds, File::findDirectories, false); | |||
| for (int i = cds.size(); --i >= 0;) | |||
| if (! cds.getReference(i).getChildFile (".TOC.plist").exists()) | |||
| cds.remove (i); | |||
| } | |||
| struct TrackSorter | |||
| { | |||
| static int getCDTrackNumber (const File& file) | |||
| { | |||
| return file.getFileName().initialSectionContainingOnly ("0123456789").getIntValue(); | |||
| } | |||
| static int compareElements (const File& first, const File& second) | |||
| { | |||
| const int firstTrack = getCDTrackNumber (first); | |||
| const int secondTrack = getCDTrackNumber (second); | |||
| jassert (firstTrack > 0 && secondTrack > 0); | |||
| return firstTrack - secondTrack; | |||
| } | |||
| }; | |||
| } | |||
| //============================================================================== | |||
| StringArray AudioCDReader::getAvailableCDNames() | |||
| { | |||
| Array<File> cds; | |||
| CDReaderHelpers::findDevices (cds); | |||
| StringArray names; | |||
| for (int i = 0; i < cds.size(); ++i) | |||
| names.add (cds.getReference(i).getFileName()); | |||
| return names; | |||
| } | |||
| AudioCDReader* AudioCDReader::createReaderForCD (const int index) | |||
| { | |||
| Array<File> cds; | |||
| CDReaderHelpers::findDevices (cds); | |||
| if (cds[index].exists()) | |||
| return new AudioCDReader (cds[index]); | |||
| return nullptr; | |||
| } | |||
| AudioCDReader::AudioCDReader (const File& volume) | |||
| : AudioFormatReader (0, "CD Audio"), | |||
| volumeDir (volume), | |||
| currentReaderTrack (-1), | |||
| reader (0) | |||
| { | |||
| sampleRate = 44100.0; | |||
| bitsPerSample = 16; | |||
| numChannels = 2; | |||
| usesFloatingPointData = false; | |||
| refreshTrackLengths(); | |||
| } | |||
| AudioCDReader::~AudioCDReader() | |||
| { | |||
| } | |||
| void AudioCDReader::refreshTrackLengths() | |||
| { | |||
| tracks.clear(); | |||
| trackStartSamples.clear(); | |||
| lengthInSamples = 0; | |||
| volumeDir.findChildFiles (tracks, File::findFiles | File::ignoreHiddenFiles, false, "*.aiff"); | |||
| CDReaderHelpers::TrackSorter sorter; | |||
| tracks.sort (sorter); | |||
| const File toc (volumeDir.getChildFile (".TOC.plist")); | |||
| if (toc.exists()) | |||
| { | |||
| XmlDocument doc (toc); | |||
| const char* error = CDReaderHelpers::getTrackOffsets (doc, trackStartSamples); | |||
| (void) error; // could be logged.. | |||
| lengthInSamples = trackStartSamples.getLast() - trackStartSamples.getFirst(); | |||
| } | |||
| } | |||
| bool AudioCDReader::readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | |||
| int64 startSampleInFile, int numSamples) | |||
| { | |||
| while (numSamples > 0) | |||
| { | |||
| int track = -1; | |||
| for (int i = 0; i < trackStartSamples.size() - 1; ++i) | |||
| { | |||
| if (startSampleInFile < trackStartSamples.getUnchecked (i + 1)) | |||
| { | |||
| track = i; | |||
| break; | |||
| } | |||
| } | |||
| if (track < 0) | |||
| return false; | |||
| if (track != currentReaderTrack) | |||
| { | |||
| reader = nullptr; | |||
| if (FileInputStream* const in = tracks [track].createInputStream()) | |||
| { | |||
| BufferedInputStream* const bin = new BufferedInputStream (in, 65536, true); | |||
| AiffAudioFormat format; | |||
| reader = format.createReaderFor (bin, true); | |||
| if (reader == nullptr) | |||
| currentReaderTrack = -1; | |||
| else | |||
| currentReaderTrack = track; | |||
| } | |||
| } | |||
| if (reader == nullptr) | |||
| return false; | |||
| const int startPos = (int) (startSampleInFile - trackStartSamples.getUnchecked (track)); | |||
| const int numAvailable = (int) jmin ((int64) numSamples, reader->lengthInSamples - startPos); | |||
| reader->readSamples (destSamples, numDestChannels, startOffsetInDestBuffer, startPos, numAvailable); | |||
| numSamples -= numAvailable; | |||
| startSampleInFile += numAvailable; | |||
| } | |||
| return true; | |||
| } | |||
| bool AudioCDReader::isCDStillPresent() const | |||
| { | |||
| return volumeDir.exists(); | |||
| } | |||
| void AudioCDReader::ejectDisk() | |||
| { | |||
| JUCE_AUTORELEASEPOOL | |||
| { | |||
| [[NSWorkspace sharedWorkspace] unmountAndEjectDeviceAtPath: juceStringToNS (volumeDir.getFullPathName())]; | |||
| } | |||
| } | |||
| bool AudioCDReader::isTrackAudio (int trackNum) const | |||
| { | |||
| return tracks [trackNum].hasFileExtension (".aiff"); | |||
| } | |||
| void AudioCDReader::enableIndexScanning (bool) | |||
| { | |||
| // any way to do this on a Mac?? | |||
| } | |||
| int AudioCDReader::getLastIndex() const | |||
| { | |||
| return 0; | |||
| } | |||
| Array<int> AudioCDReader::findIndexesInTrack (const int /*trackNumber*/) | |||
| { | |||
| return Array<int>(); | |||
| } | |||
| @@ -1,530 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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_LOG_COREMIDI_ERRORS | |||
| #define JUCE_LOG_COREMIDI_ERRORS 1 | |||
| #endif | |||
| namespace CoreMidiHelpers | |||
| { | |||
| static bool checkError (const OSStatus err, const int lineNum) | |||
| { | |||
| if (err == noErr) | |||
| return true; | |||
| #if JUCE_LOG_COREMIDI_ERRORS | |||
| Logger::writeToLog ("CoreMIDI error: " + String (lineNum) + " - " + String::toHexString ((int) err)); | |||
| #endif | |||
| (void) lineNum; | |||
| return false; | |||
| } | |||
| #undef CHECK_ERROR | |||
| #define CHECK_ERROR(a) CoreMidiHelpers::checkError (a, __LINE__) | |||
| //============================================================================== | |||
| static String getMidiObjectName (MIDIObjectRef entity) | |||
| { | |||
| String result; | |||
| CFStringRef str = nullptr; | |||
| MIDIObjectGetStringProperty (entity, kMIDIPropertyName, &str); | |||
| if (str != nullptr) | |||
| { | |||
| result = String::fromCFString (str); | |||
| CFRelease (str); | |||
| } | |||
| return result; | |||
| } | |||
| static String getEndpointName (MIDIEndpointRef endpoint, bool isExternal) | |||
| { | |||
| String result (getMidiObjectName (endpoint)); | |||
| MIDIEntityRef entity = 0; // NB: don't attempt to use nullptr for refs - it fails in some types of build. | |||
| MIDIEndpointGetEntity (endpoint, &entity); | |||
| if (entity == 0) | |||
| return result; // probably virtual | |||
| if (result.isEmpty()) | |||
| result = getMidiObjectName (entity); // endpoint name is empty - try the entity | |||
| // now consider the device's name | |||
| MIDIDeviceRef device = 0; | |||
| MIDIEntityGetDevice (entity, &device); | |||
| if (device != 0) | |||
| { | |||
| const String deviceName (getMidiObjectName (device)); | |||
| if (deviceName.isNotEmpty()) | |||
| { | |||
| // if an external device has only one entity, throw away | |||
| // the endpoint name and just use the device name | |||
| if (isExternal && MIDIDeviceGetNumberOfEntities (device) < 2) | |||
| { | |||
| result = deviceName; | |||
| } | |||
| else if (! result.startsWithIgnoreCase (deviceName)) | |||
| { | |||
| // prepend the device name to the entity name | |||
| result = (deviceName + " " + result).trimEnd(); | |||
| } | |||
| } | |||
| } | |||
| return result; | |||
| } | |||
| static String getConnectedEndpointName (MIDIEndpointRef endpoint) | |||
| { | |||
| String result; | |||
| // Does the endpoint have connections? | |||
| CFDataRef connections = nullptr; | |||
| int numConnections = 0; | |||
| MIDIObjectGetDataProperty (endpoint, kMIDIPropertyConnectionUniqueID, &connections); | |||
| if (connections != nullptr) | |||
| { | |||
| numConnections = ((int) CFDataGetLength (connections)) / (int) sizeof (MIDIUniqueID); | |||
| if (numConnections > 0) | |||
| { | |||
| const SInt32* pid = reinterpret_cast <const SInt32*> (CFDataGetBytePtr (connections)); | |||
| for (int i = 0; i < numConnections; ++i, ++pid) | |||
| { | |||
| MIDIUniqueID uid = (MIDIUniqueID) ByteOrder::swapIfLittleEndian ((uint32) *pid); | |||
| MIDIObjectRef connObject; | |||
| MIDIObjectType connObjectType; | |||
| OSStatus err = MIDIObjectFindByUniqueID (uid, &connObject, &connObjectType); | |||
| if (err == noErr) | |||
| { | |||
| String s; | |||
| if (connObjectType == kMIDIObjectType_ExternalSource | |||
| || connObjectType == kMIDIObjectType_ExternalDestination) | |||
| { | |||
| // Connected to an external device's endpoint (10.3 and later). | |||
| s = getEndpointName (static_cast <MIDIEndpointRef> (connObject), true); | |||
| } | |||
| else | |||
| { | |||
| // Connected to an external device (10.2) (or something else, catch-all) | |||
| s = getMidiObjectName (connObject); | |||
| } | |||
| if (s.isNotEmpty()) | |||
| { | |||
| if (result.isNotEmpty()) | |||
| result += ", "; | |||
| result += s; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| CFRelease (connections); | |||
| } | |||
| if (result.isEmpty()) // Here, either the endpoint had no connections, or we failed to obtain names for them. | |||
| result = getEndpointName (endpoint, false); | |||
| return result; | |||
| } | |||
| static StringArray findDevices (const bool forInput) | |||
| { | |||
| const ItemCount num = forInput ? MIDIGetNumberOfSources() | |||
| : MIDIGetNumberOfDestinations(); | |||
| StringArray s; | |||
| for (ItemCount i = 0; i < num; ++i) | |||
| { | |||
| MIDIEndpointRef dest = forInput ? MIDIGetSource (i) | |||
| : MIDIGetDestination (i); | |||
| String name; | |||
| if (dest != 0) | |||
| name = getConnectedEndpointName (dest); | |||
| if (name.isEmpty()) | |||
| name = "<error>"; | |||
| s.add (name); | |||
| } | |||
| return s; | |||
| } | |||
| static void globalSystemChangeCallback (const MIDINotification*, void*) | |||
| { | |||
| // TODO.. Should pass-on this notification.. | |||
| } | |||
| static String getGlobalMidiClientName() | |||
| { | |||
| if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance()) | |||
| return app->getApplicationName(); | |||
| return "JUCE"; | |||
| } | |||
| static MIDIClientRef getGlobalMidiClient() | |||
| { | |||
| static MIDIClientRef globalMidiClient = 0; | |||
| if (globalMidiClient == 0) | |||
| { | |||
| // Since OSX 10.6, the MIDIClientCreate function will only work | |||
| // correctly when called from the message thread! | |||
| jassert (MessageManager::getInstance()->isThisTheMessageThread()); | |||
| CFStringRef name = getGlobalMidiClientName().toCFString(); | |||
| CHECK_ERROR (MIDIClientCreate (name, &globalSystemChangeCallback, nullptr, &globalMidiClient)); | |||
| CFRelease (name); | |||
| } | |||
| return globalMidiClient; | |||
| } | |||
| //============================================================================== | |||
| class MidiPortAndEndpoint | |||
| { | |||
| public: | |||
| MidiPortAndEndpoint (MIDIPortRef p, MIDIEndpointRef ep) | |||
| : port (p), endPoint (ep) | |||
| { | |||
| } | |||
| ~MidiPortAndEndpoint() | |||
| { | |||
| if (port != 0) | |||
| MIDIPortDispose (port); | |||
| if (port == 0 && endPoint != 0) // if port == nullptr, it means we created the endpoint, so it's safe to delete it | |||
| MIDIEndpointDispose (endPoint); | |||
| } | |||
| void send (const MIDIPacketList* const packets) | |||
| { | |||
| if (port != 0) | |||
| MIDISend (port, endPoint, packets); | |||
| else | |||
| MIDIReceived (endPoint, packets); | |||
| } | |||
| MIDIPortRef port; | |||
| MIDIEndpointRef endPoint; | |||
| }; | |||
| //============================================================================== | |||
| class MidiPortAndCallback; | |||
| CriticalSection callbackLock; | |||
| Array<MidiPortAndCallback*> activeCallbacks; | |||
| class MidiPortAndCallback | |||
| { | |||
| public: | |||
| MidiPortAndCallback (MidiInputCallback& cb) | |||
| : input (nullptr), active (false), callback (cb), concatenator (2048) | |||
| { | |||
| } | |||
| ~MidiPortAndCallback() | |||
| { | |||
| active = false; | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| activeCallbacks.removeFirstMatchingValue (this); | |||
| } | |||
| if (portAndEndpoint != 0 && portAndEndpoint->port != 0) | |||
| CHECK_ERROR (MIDIPortDisconnectSource (portAndEndpoint->port, portAndEndpoint->endPoint)); | |||
| } | |||
| void handlePackets (const MIDIPacketList* const pktlist) | |||
| { | |||
| const double time = Time::getMillisecondCounterHiRes() * 0.001; | |||
| const ScopedLock sl (callbackLock); | |||
| if (activeCallbacks.contains (this) && active) | |||
| { | |||
| const MIDIPacket* packet = &pktlist->packet[0]; | |||
| for (unsigned int i = 0; i < pktlist->numPackets; ++i) | |||
| { | |||
| concatenator.pushMidiData (packet->data, (int) packet->length, time, | |||
| input, callback); | |||
| packet = MIDIPacketNext (packet); | |||
| } | |||
| } | |||
| } | |||
| MidiInput* input; | |||
| ScopedPointer<MidiPortAndEndpoint> portAndEndpoint; | |||
| volatile bool active; | |||
| private: | |||
| MidiInputCallback& callback; | |||
| MidiDataConcatenator concatenator; | |||
| }; | |||
| static void midiInputProc (const MIDIPacketList* pktlist, void* readProcRefCon, void* /*srcConnRefCon*/) | |||
| { | |||
| static_cast <MidiPortAndCallback*> (readProcRefCon)->handlePackets (pktlist); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| StringArray MidiOutput::getDevices() { return CoreMidiHelpers::findDevices (false); } | |||
| int MidiOutput::getDefaultDeviceIndex() { return 0; } | |||
| MidiOutput* MidiOutput::openDevice (int index) | |||
| { | |||
| MidiOutput* mo = nullptr; | |||
| if (isPositiveAndBelow (index, (int) MIDIGetNumberOfDestinations())) | |||
| { | |||
| MIDIEndpointRef endPoint = MIDIGetDestination ((ItemCount) index); | |||
| CFStringRef pname; | |||
| if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &pname))) | |||
| { | |||
| MIDIClientRef client = CoreMidiHelpers::getGlobalMidiClient(); | |||
| MIDIPortRef port; | |||
| if (client != 0 && CHECK_ERROR (MIDIOutputPortCreate (client, pname, &port))) | |||
| { | |||
| mo = new MidiOutput(); | |||
| mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (port, endPoint); | |||
| } | |||
| CFRelease (pname); | |||
| } | |||
| } | |||
| return mo; | |||
| } | |||
| MidiOutput* MidiOutput::createNewDevice (const String& deviceName) | |||
| { | |||
| MidiOutput* mo = nullptr; | |||
| MIDIClientRef client = CoreMidiHelpers::getGlobalMidiClient(); | |||
| MIDIEndpointRef endPoint; | |||
| CFStringRef name = deviceName.toCFString(); | |||
| if (client != 0 && CHECK_ERROR (MIDISourceCreate (client, name, &endPoint))) | |||
| { | |||
| mo = new MidiOutput(); | |||
| mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (0, endPoint); | |||
| } | |||
| CFRelease (name); | |||
| return mo; | |||
| } | |||
| MidiOutput::~MidiOutput() | |||
| { | |||
| stopBackgroundThread(); | |||
| delete static_cast<CoreMidiHelpers::MidiPortAndEndpoint*> (internal); | |||
| } | |||
| void MidiOutput::sendMessageNow (const MidiMessage& message) | |||
| { | |||
| #if JUCE_IOS | |||
| const MIDITimeStamp timeStamp = mach_absolute_time(); | |||
| #else | |||
| const MIDITimeStamp timeStamp = AudioGetCurrentHostTime(); | |||
| #endif | |||
| HeapBlock <MIDIPacketList> allocatedPackets; | |||
| MIDIPacketList stackPacket; | |||
| MIDIPacketList* packetToSend = &stackPacket; | |||
| const size_t dataSize = (size_t) message.getRawDataSize(); | |||
| if (message.isSysEx()) | |||
| { | |||
| const int maxPacketSize = 256; | |||
| int pos = 0, bytesLeft = (int) dataSize; | |||
| const int numPackets = (bytesLeft + maxPacketSize - 1) / maxPacketSize; | |||
| allocatedPackets.malloc ((size_t) (32 * (size_t) numPackets + dataSize), 1); | |||
| packetToSend = allocatedPackets; | |||
| packetToSend->numPackets = (UInt32) numPackets; | |||
| MIDIPacket* p = packetToSend->packet; | |||
| for (int i = 0; i < numPackets; ++i) | |||
| { | |||
| p->timeStamp = timeStamp; | |||
| p->length = (UInt16) jmin (maxPacketSize, bytesLeft); | |||
| memcpy (p->data, message.getRawData() + pos, p->length); | |||
| pos += p->length; | |||
| bytesLeft -= p->length; | |||
| p = MIDIPacketNext (p); | |||
| } | |||
| } | |||
| else if (dataSize < 65536) // max packet size | |||
| { | |||
| const size_t stackCapacity = sizeof (stackPacket.packet->data); | |||
| if (dataSize > stackCapacity) | |||
| { | |||
| allocatedPackets.malloc ((sizeof (MIDIPacketList) - stackCapacity) + dataSize, 1); | |||
| packetToSend = allocatedPackets; | |||
| } | |||
| packetToSend->numPackets = 1; | |||
| MIDIPacket& p = *(packetToSend->packet); | |||
| p.timeStamp = timeStamp; | |||
| p.length = (UInt16) dataSize; | |||
| memcpy (p.data, message.getRawData(), dataSize); | |||
| } | |||
| else | |||
| { | |||
| jassertfalse; // packet too large to send! | |||
| return; | |||
| } | |||
| static_cast<CoreMidiHelpers::MidiPortAndEndpoint*> (internal)->send (packetToSend); | |||
| } | |||
| //============================================================================== | |||
| StringArray MidiInput::getDevices() { return CoreMidiHelpers::findDevices (true); } | |||
| int MidiInput::getDefaultDeviceIndex() { return 0; } | |||
| MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) | |||
| { | |||
| jassert (callback != nullptr); | |||
| using namespace CoreMidiHelpers; | |||
| MidiInput* newInput = nullptr; | |||
| if (isPositiveAndBelow (index, (int) MIDIGetNumberOfSources())) | |||
| { | |||
| if (MIDIEndpointRef endPoint = MIDIGetSource ((ItemCount) index)) | |||
| { | |||
| CFStringRef name; | |||
| if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &name))) | |||
| { | |||
| if (MIDIClientRef client = getGlobalMidiClient()) | |||
| { | |||
| MIDIPortRef port; | |||
| ScopedPointer <MidiPortAndCallback> mpc (new MidiPortAndCallback (*callback)); | |||
| if (CHECK_ERROR (MIDIInputPortCreate (client, name, midiInputProc, mpc, &port))) | |||
| { | |||
| if (CHECK_ERROR (MIDIPortConnectSource (port, endPoint, nullptr))) | |||
| { | |||
| mpc->portAndEndpoint = new MidiPortAndEndpoint (port, endPoint); | |||
| newInput = new MidiInput (getDevices() [index]); | |||
| mpc->input = newInput; | |||
| newInput->internal = mpc; | |||
| const ScopedLock sl (callbackLock); | |||
| activeCallbacks.add (mpc.release()); | |||
| } | |||
| else | |||
| { | |||
| CHECK_ERROR (MIDIPortDispose (port)); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| CFRelease (name); | |||
| } | |||
| } | |||
| return newInput; | |||
| } | |||
| MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) | |||
| { | |||
| jassert (callback != nullptr); | |||
| using namespace CoreMidiHelpers; | |||
| MidiInput* mi = nullptr; | |||
| if (MIDIClientRef client = getGlobalMidiClient()) | |||
| { | |||
| ScopedPointer <MidiPortAndCallback> mpc (new MidiPortAndCallback (*callback)); | |||
| mpc->active = false; | |||
| MIDIEndpointRef endPoint; | |||
| CFStringRef name = deviceName.toCFString(); | |||
| if (CHECK_ERROR (MIDIDestinationCreate (client, name, midiInputProc, mpc, &endPoint))) | |||
| { | |||
| mpc->portAndEndpoint = new MidiPortAndEndpoint (0, endPoint); | |||
| mi = new MidiInput (deviceName); | |||
| mpc->input = mi; | |||
| mi->internal = mpc; | |||
| const ScopedLock sl (callbackLock); | |||
| activeCallbacks.add (mpc.release()); | |||
| } | |||
| CFRelease (name); | |||
| } | |||
| return mi; | |||
| } | |||
| MidiInput::MidiInput (const String& nm) : name (nm) | |||
| { | |||
| } | |||
| MidiInput::~MidiInput() | |||
| { | |||
| delete static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal); | |||
| } | |||
| void MidiInput::start() | |||
| { | |||
| const ScopedLock sl (CoreMidiHelpers::callbackLock); | |||
| static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal)->active = true; | |||
| } | |||
| void MidiInput::stop() | |||
| { | |||
| const ScopedLock sl (CoreMidiHelpers::callbackLock); | |||
| static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal)->active = false; | |||
| } | |||
| #undef CHECK_ERROR | |||
| @@ -1,411 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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 CDBurnerHelpers | |||
| { | |||
| IDiscRecorder* enumCDBurners (StringArray* list, int indexToOpen, IDiscMaster** master) | |||
| { | |||
| CoInitialize (0); | |||
| IDiscMaster* dm; | |||
| IDiscRecorder* result = nullptr; | |||
| if (SUCCEEDED (CoCreateInstance (CLSID_MSDiscMasterObj, 0, | |||
| CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER, | |||
| IID_IDiscMaster, | |||
| (void**) &dm))) | |||
| { | |||
| if (SUCCEEDED (dm->Open())) | |||
| { | |||
| IEnumDiscRecorders* drEnum = nullptr; | |||
| if (SUCCEEDED (dm->EnumDiscRecorders (&drEnum))) | |||
| { | |||
| IDiscRecorder* dr = nullptr; | |||
| DWORD dummy; | |||
| int index = 0; | |||
| while (drEnum->Next (1, &dr, &dummy) == S_OK) | |||
| { | |||
| if (indexToOpen == index) | |||
| { | |||
| result = dr; | |||
| break; | |||
| } | |||
| else if (list != nullptr) | |||
| { | |||
| BSTR path; | |||
| if (SUCCEEDED (dr->GetPath (&path))) | |||
| list->add ((const WCHAR*) path); | |||
| } | |||
| ++index; | |||
| dr->Release(); | |||
| } | |||
| drEnum->Release(); | |||
| } | |||
| if (master == 0) | |||
| dm->Close(); | |||
| } | |||
| if (master != nullptr) | |||
| *master = dm; | |||
| else | |||
| dm->Release(); | |||
| } | |||
| return result; | |||
| } | |||
| } | |||
| //============================================================================== | |||
| class AudioCDBurner::Pimpl : public ComBaseClassHelper <IDiscMasterProgressEvents>, | |||
| public Timer | |||
| { | |||
| public: | |||
| Pimpl (AudioCDBurner& owner_, IDiscMaster* discMaster_, IDiscRecorder* discRecorder_) | |||
| : owner (owner_), discMaster (discMaster_), discRecorder (discRecorder_), redbook (0), | |||
| listener (0), progress (0), shouldCancel (false) | |||
| { | |||
| HRESULT hr = discMaster->SetActiveDiscMasterFormat (IID_IRedbookDiscMaster, (void**) &redbook); | |||
| jassert (SUCCEEDED (hr)); | |||
| hr = discMaster->SetActiveDiscRecorder (discRecorder); | |||
| //jassert (SUCCEEDED (hr)); | |||
| lastState = getDiskState(); | |||
| startTimer (2000); | |||
| } | |||
| ~Pimpl() {} | |||
| void releaseObjects() | |||
| { | |||
| discRecorder->Close(); | |||
| if (redbook != nullptr) | |||
| redbook->Release(); | |||
| discRecorder->Release(); | |||
| discMaster->Release(); | |||
| Release(); | |||
| } | |||
| JUCE_COMRESULT QueryCancel (boolean* pbCancel) | |||
| { | |||
| if (listener != nullptr && ! shouldCancel) | |||
| shouldCancel = listener->audioCDBurnProgress (progress); | |||
| *pbCancel = shouldCancel; | |||
| return S_OK; | |||
| } | |||
| JUCE_COMRESULT NotifyBlockProgress (long nCompleted, long nTotal) | |||
| { | |||
| progress = nCompleted / (float) nTotal; | |||
| shouldCancel = listener != nullptr && listener->audioCDBurnProgress (progress); | |||
| return E_NOTIMPL; | |||
| } | |||
| JUCE_COMRESULT NotifyPnPActivity (void) { return E_NOTIMPL; } | |||
| JUCE_COMRESULT NotifyAddProgress (long /*nCompletedSteps*/, long /*nTotalSteps*/) { return E_NOTIMPL; } | |||
| JUCE_COMRESULT NotifyTrackProgress (long /*nCurrentTrack*/, long /*nTotalTracks*/) { return E_NOTIMPL; } | |||
| JUCE_COMRESULT NotifyPreparingBurn (long /*nEstimatedSeconds*/) { return E_NOTIMPL; } | |||
| JUCE_COMRESULT NotifyClosingDisc (long /*nEstimatedSeconds*/) { return E_NOTIMPL; } | |||
| JUCE_COMRESULT NotifyBurnComplete (HRESULT /*status*/) { return E_NOTIMPL; } | |||
| JUCE_COMRESULT NotifyEraseComplete (HRESULT /*status*/) { return E_NOTIMPL; } | |||
| class ScopedDiscOpener | |||
| { | |||
| public: | |||
| ScopedDiscOpener (Pimpl& p) : pimpl (p) { pimpl.discRecorder->OpenExclusive(); } | |||
| ~ScopedDiscOpener() { pimpl.discRecorder->Close(); } | |||
| private: | |||
| Pimpl& pimpl; | |||
| JUCE_DECLARE_NON_COPYABLE (ScopedDiscOpener) | |||
| }; | |||
| DiskState getDiskState() | |||
| { | |||
| const ScopedDiscOpener opener (*this); | |||
| long type, flags; | |||
| HRESULT hr = discRecorder->QueryMediaType (&type, &flags); | |||
| if (FAILED (hr)) | |||
| return unknown; | |||
| if (type != 0 && (flags & MEDIA_WRITABLE) != 0) | |||
| return writableDiskPresent; | |||
| if (type == 0) | |||
| return noDisc; | |||
| return readOnlyDiskPresent; | |||
| } | |||
| int getIntProperty (const LPOLESTR name, const int defaultReturn) const | |||
| { | |||
| ComSmartPtr<IPropertyStorage> prop; | |||
| if (FAILED (discRecorder->GetRecorderProperties (prop.resetAndGetPointerAddress()))) | |||
| return defaultReturn; | |||
| PROPSPEC iPropSpec; | |||
| iPropSpec.ulKind = PRSPEC_LPWSTR; | |||
| iPropSpec.lpwstr = name; | |||
| PROPVARIANT iPropVariant; | |||
| return FAILED (prop->ReadMultiple (1, &iPropSpec, &iPropVariant)) | |||
| ? defaultReturn : (int) iPropVariant.lVal; | |||
| } | |||
| bool setIntProperty (const LPOLESTR name, const int value) const | |||
| { | |||
| ComSmartPtr<IPropertyStorage> prop; | |||
| if (FAILED (discRecorder->GetRecorderProperties (prop.resetAndGetPointerAddress()))) | |||
| return false; | |||
| PROPSPEC iPropSpec; | |||
| iPropSpec.ulKind = PRSPEC_LPWSTR; | |||
| iPropSpec.lpwstr = name; | |||
| PROPVARIANT iPropVariant; | |||
| if (FAILED (prop->ReadMultiple (1, &iPropSpec, &iPropVariant))) | |||
| return false; | |||
| iPropVariant.lVal = (long) value; | |||
| return SUCCEEDED (prop->WriteMultiple (1, &iPropSpec, &iPropVariant, iPropVariant.vt)) | |||
| && SUCCEEDED (discRecorder->SetRecorderProperties (prop)); | |||
| } | |||
| void timerCallback() override | |||
| { | |||
| const DiskState state = getDiskState(); | |||
| if (state != lastState) | |||
| { | |||
| lastState = state; | |||
| owner.sendChangeMessage(); | |||
| } | |||
| } | |||
| AudioCDBurner& owner; | |||
| DiskState lastState; | |||
| IDiscMaster* discMaster; | |||
| IDiscRecorder* discRecorder; | |||
| IRedbookDiscMaster* redbook; | |||
| AudioCDBurner::BurnProgressListener* listener; | |||
| float progress; | |||
| bool shouldCancel; | |||
| }; | |||
| //============================================================================== | |||
| AudioCDBurner::AudioCDBurner (const int deviceIndex) | |||
| { | |||
| IDiscMaster* discMaster = nullptr; | |||
| IDiscRecorder* discRecorder = CDBurnerHelpers::enumCDBurners (0, deviceIndex, &discMaster); | |||
| if (discRecorder != nullptr) | |||
| pimpl = new Pimpl (*this, discMaster, discRecorder); | |||
| } | |||
| AudioCDBurner::~AudioCDBurner() | |||
| { | |||
| if (pimpl != nullptr) | |||
| pimpl.release()->releaseObjects(); | |||
| } | |||
| StringArray AudioCDBurner::findAvailableDevices() | |||
| { | |||
| StringArray devs; | |||
| CDBurnerHelpers::enumCDBurners (&devs, -1, 0); | |||
| return devs; | |||
| } | |||
| AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex) | |||
| { | |||
| ScopedPointer<AudioCDBurner> b (new AudioCDBurner (deviceIndex)); | |||
| if (b->pimpl == 0) | |||
| b = nullptr; | |||
| return b.release(); | |||
| } | |||
| AudioCDBurner::DiskState AudioCDBurner::getDiskState() const | |||
| { | |||
| return pimpl->getDiskState(); | |||
| } | |||
| bool AudioCDBurner::isDiskPresent() const | |||
| { | |||
| return getDiskState() == writableDiskPresent; | |||
| } | |||
| bool AudioCDBurner::openTray() | |||
| { | |||
| const Pimpl::ScopedDiscOpener opener (*pimpl); | |||
| return SUCCEEDED (pimpl->discRecorder->Eject()); | |||
| } | |||
| AudioCDBurner::DiskState AudioCDBurner::waitUntilStateChange (int timeOutMilliseconds) | |||
| { | |||
| const int64 timeout = Time::currentTimeMillis() + timeOutMilliseconds; | |||
| DiskState oldState = getDiskState(); | |||
| DiskState newState = oldState; | |||
| while (newState == oldState && Time::currentTimeMillis() < timeout) | |||
| { | |||
| newState = getDiskState(); | |||
| Thread::sleep (jmin (250, (int) (timeout - Time::currentTimeMillis()))); | |||
| } | |||
| return newState; | |||
| } | |||
| Array<int> AudioCDBurner::getAvailableWriteSpeeds() const | |||
| { | |||
| Array<int> results; | |||
| const int maxSpeed = pimpl->getIntProperty (L"MaxWriteSpeed", 1); | |||
| const int speeds[] = { 1, 2, 4, 8, 12, 16, 20, 24, 32, 40, 64, 80 }; | |||
| for (int i = 0; i < numElementsInArray (speeds); ++i) | |||
| if (speeds[i] <= maxSpeed) | |||
| results.add (speeds[i]); | |||
| results.addIfNotAlreadyThere (maxSpeed); | |||
| return results; | |||
| } | |||
| bool AudioCDBurner::setBufferUnderrunProtection (const bool shouldBeEnabled) | |||
| { | |||
| if (pimpl->getIntProperty (L"BufferUnderrunFreeCapable", 0) == 0) | |||
| return false; | |||
| pimpl->setIntProperty (L"EnableBufferUnderrunFree", shouldBeEnabled ? -1 : 0); | |||
| return pimpl->getIntProperty (L"EnableBufferUnderrunFree", 0) != 0; | |||
| } | |||
| int AudioCDBurner::getNumAvailableAudioBlocks() const | |||
| { | |||
| long blocksFree = 0; | |||
| pimpl->redbook->GetAvailableAudioTrackBlocks (&blocksFree); | |||
| return blocksFree; | |||
| } | |||
| String AudioCDBurner::burn (AudioCDBurner::BurnProgressListener* listener, bool ejectDiscAfterwards, | |||
| bool performFakeBurnForTesting, int writeSpeed) | |||
| { | |||
| pimpl->setIntProperty (L"WriteSpeed", writeSpeed > 0 ? writeSpeed : -1); | |||
| pimpl->listener = listener; | |||
| pimpl->progress = 0; | |||
| pimpl->shouldCancel = false; | |||
| UINT_PTR cookie; | |||
| HRESULT hr = pimpl->discMaster->ProgressAdvise ((AudioCDBurner::Pimpl*) pimpl, &cookie); | |||
| hr = pimpl->discMaster->RecordDisc (performFakeBurnForTesting, | |||
| ejectDiscAfterwards); | |||
| String error; | |||
| if (hr != S_OK) | |||
| { | |||
| const char* e = "Couldn't open or write to the CD device"; | |||
| if (hr == IMAPI_E_USERABORT) | |||
| e = "User cancelled the write operation"; | |||
| else if (hr == IMAPI_E_MEDIUM_NOTPRESENT || hr == IMAPI_E_TRACKOPEN) | |||
| e = "No Disk present"; | |||
| error = e; | |||
| } | |||
| pimpl->discMaster->ProgressUnadvise (cookie); | |||
| pimpl->listener = 0; | |||
| return error; | |||
| } | |||
| bool AudioCDBurner::addAudioTrack (AudioSource* audioSource, int numSamples) | |||
| { | |||
| if (audioSource == 0) | |||
| return false; | |||
| ScopedPointer<AudioSource> source (audioSource); | |||
| long bytesPerBlock; | |||
| HRESULT hr = pimpl->redbook->GetAudioBlockSize (&bytesPerBlock); | |||
| const int samplesPerBlock = bytesPerBlock / 4; | |||
| bool ok = true; | |||
| hr = pimpl->redbook->CreateAudioTrack ((long) numSamples / (bytesPerBlock * 4)); | |||
| HeapBlock <byte> buffer (bytesPerBlock); | |||
| AudioSampleBuffer sourceBuffer (2, samplesPerBlock); | |||
| int samplesDone = 0; | |||
| source->prepareToPlay (samplesPerBlock, 44100.0); | |||
| while (ok) | |||
| { | |||
| { | |||
| AudioSourceChannelInfo info (&sourceBuffer, 0, samplesPerBlock); | |||
| sourceBuffer.clear(); | |||
| source->getNextAudioBlock (info); | |||
| } | |||
| buffer.clear (bytesPerBlock); | |||
| typedef AudioData::Pointer <AudioData::Int16, AudioData::LittleEndian, | |||
| AudioData::Interleaved, AudioData::NonConst> CDSampleFormat; | |||
| typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, | |||
| AudioData::NonInterleaved, AudioData::Const> SourceSampleFormat; | |||
| CDSampleFormat left (buffer, 2); | |||
| left.convertSamples (SourceSampleFormat (sourceBuffer.getReadPointer (0)), samplesPerBlock); | |||
| CDSampleFormat right (buffer + 2, 2); | |||
| right.convertSamples (SourceSampleFormat (sourceBuffer.getReadPointer (1)), samplesPerBlock); | |||
| hr = pimpl->redbook->AddAudioTrackBlocks (buffer, bytesPerBlock); | |||
| if (FAILED (hr)) | |||
| ok = false; | |||
| samplesDone += samplesPerBlock; | |||
| if (samplesDone >= numSamples) | |||
| break; | |||
| } | |||
| hr = pimpl->redbook->CloseAudioTrack(); | |||
| return ok && hr == S_OK; | |||
| } | |||
| @@ -1,489 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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. | |||
| ============================================================================== | |||
| */ | |||
| class MidiInCollector | |||
| { | |||
| public: | |||
| MidiInCollector (MidiInput* const input_, | |||
| MidiInputCallback& callback_) | |||
| : deviceHandle (0), | |||
| input (input_), | |||
| callback (callback_), | |||
| concatenator (4096), | |||
| isStarted (false), | |||
| startTime (0) | |||
| { | |||
| } | |||
| ~MidiInCollector() | |||
| { | |||
| stop(); | |||
| if (deviceHandle != 0) | |||
| { | |||
| for (int count = 5; --count >= 0;) | |||
| { | |||
| if (midiInClose (deviceHandle) == MMSYSERR_NOERROR) | |||
| break; | |||
| Sleep (20); | |||
| } | |||
| } | |||
| } | |||
| //============================================================================== | |||
| void handleMessage (const uint8* bytes, const uint32 timeStamp) | |||
| { | |||
| if (bytes[0] >= 0x80 && isStarted) | |||
| { | |||
| concatenator.pushMidiData (bytes, MidiMessage::getMessageLengthFromFirstByte (bytes[0]), | |||
| convertTimeStamp (timeStamp), input, callback); | |||
| writeFinishedBlocks(); | |||
| } | |||
| } | |||
| void handleSysEx (MIDIHDR* const hdr, const uint32 timeStamp) | |||
| { | |||
| if (isStarted && hdr->dwBytesRecorded > 0) | |||
| { | |||
| concatenator.pushMidiData (hdr->lpData, (int) hdr->dwBytesRecorded, | |||
| convertTimeStamp (timeStamp), input, callback); | |||
| writeFinishedBlocks(); | |||
| } | |||
| } | |||
| void start() | |||
| { | |||
| if (deviceHandle != 0 && ! isStarted) | |||
| { | |||
| activeMidiCollectors.addIfNotAlreadyThere (this); | |||
| for (int i = 0; i < (int) numHeaders; ++i) | |||
| { | |||
| headers[i].prepare (deviceHandle); | |||
| headers[i].write (deviceHandle); | |||
| } | |||
| startTime = Time::getMillisecondCounterHiRes(); | |||
| MMRESULT res = midiInStart (deviceHandle); | |||
| if (res == MMSYSERR_NOERROR) | |||
| { | |||
| concatenator.reset(); | |||
| isStarted = true; | |||
| } | |||
| else | |||
| { | |||
| unprepareAllHeaders(); | |||
| } | |||
| } | |||
| } | |||
| void stop() | |||
| { | |||
| if (isStarted) | |||
| { | |||
| isStarted = false; | |||
| midiInReset (deviceHandle); | |||
| midiInStop (deviceHandle); | |||
| activeMidiCollectors.removeFirstMatchingValue (this); | |||
| unprepareAllHeaders(); | |||
| concatenator.reset(); | |||
| } | |||
| } | |||
| static void CALLBACK midiInCallback (HMIDIIN, UINT uMsg, DWORD_PTR dwInstance, | |||
| DWORD_PTR midiMessage, DWORD_PTR timeStamp) | |||
| { | |||
| MidiInCollector* const collector = reinterpret_cast <MidiInCollector*> (dwInstance); | |||
| if (activeMidiCollectors.contains (collector)) | |||
| { | |||
| if (uMsg == MIM_DATA) | |||
| collector->handleMessage ((const uint8*) &midiMessage, (uint32) timeStamp); | |||
| else if (uMsg == MIM_LONGDATA) | |||
| collector->handleSysEx ((MIDIHDR*) midiMessage, (uint32) timeStamp); | |||
| } | |||
| } | |||
| HMIDIIN deviceHandle; | |||
| private: | |||
| static Array <MidiInCollector*, CriticalSection> activeMidiCollectors; | |||
| MidiInput* input; | |||
| MidiInputCallback& callback; | |||
| MidiDataConcatenator concatenator; | |||
| bool volatile isStarted; | |||
| double startTime; | |||
| class MidiHeader | |||
| { | |||
| public: | |||
| MidiHeader() {} | |||
| void prepare (HMIDIIN deviceHandle) | |||
| { | |||
| zerostruct (hdr); | |||
| hdr.lpData = data; | |||
| hdr.dwBufferLength = (DWORD) numElementsInArray (data); | |||
| midiInPrepareHeader (deviceHandle, &hdr, sizeof (hdr)); | |||
| } | |||
| void unprepare (HMIDIIN deviceHandle) | |||
| { | |||
| if ((hdr.dwFlags & WHDR_DONE) != 0) | |||
| { | |||
| int c = 10; | |||
| while (--c >= 0 && midiInUnprepareHeader (deviceHandle, &hdr, sizeof (hdr)) == MIDIERR_STILLPLAYING) | |||
| Thread::sleep (20); | |||
| jassert (c >= 0); | |||
| } | |||
| } | |||
| void write (HMIDIIN deviceHandle) | |||
| { | |||
| hdr.dwBytesRecorded = 0; | |||
| midiInAddBuffer (deviceHandle, &hdr, sizeof (hdr)); | |||
| } | |||
| void writeIfFinished (HMIDIIN deviceHandle) | |||
| { | |||
| if ((hdr.dwFlags & WHDR_DONE) != 0) | |||
| write (deviceHandle); | |||
| } | |||
| private: | |||
| MIDIHDR hdr; | |||
| char data [256]; | |||
| JUCE_DECLARE_NON_COPYABLE (MidiHeader) | |||
| }; | |||
| enum { numHeaders = 32 }; | |||
| MidiHeader headers [numHeaders]; | |||
| void writeFinishedBlocks() | |||
| { | |||
| for (int i = 0; i < (int) numHeaders; ++i) | |||
| headers[i].writeIfFinished (deviceHandle); | |||
| } | |||
| void unprepareAllHeaders() | |||
| { | |||
| for (int i = 0; i < (int) numHeaders; ++i) | |||
| headers[i].unprepare (deviceHandle); | |||
| } | |||
| double convertTimeStamp (uint32 timeStamp) | |||
| { | |||
| double t = startTime + timeStamp; | |||
| const double now = Time::getMillisecondCounterHiRes(); | |||
| if (t > now) | |||
| { | |||
| if (t > now + 2.0) | |||
| startTime -= 1.0; | |||
| t = now; | |||
| } | |||
| return t * 0.001; | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInCollector) | |||
| }; | |||
| Array <MidiInCollector*, CriticalSection> MidiInCollector::activeMidiCollectors; | |||
| //============================================================================== | |||
| StringArray MidiInput::getDevices() | |||
| { | |||
| StringArray s; | |||
| const UINT num = midiInGetNumDevs(); | |||
| for (UINT i = 0; i < num; ++i) | |||
| { | |||
| MIDIINCAPS mc = { 0 }; | |||
| if (midiInGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) | |||
| s.add (String (mc.szPname, sizeof (mc.szPname))); | |||
| } | |||
| return s; | |||
| } | |||
| int MidiInput::getDefaultDeviceIndex() | |||
| { | |||
| return 0; | |||
| } | |||
| MidiInput* MidiInput::openDevice (const int index, MidiInputCallback* const callback) | |||
| { | |||
| if (callback == nullptr) | |||
| return nullptr; | |||
| UINT deviceId = MIDI_MAPPER; | |||
| int n = 0; | |||
| String name; | |||
| const UINT num = midiInGetNumDevs(); | |||
| for (UINT i = 0; i < num; ++i) | |||
| { | |||
| MIDIINCAPS mc = { 0 }; | |||
| if (midiInGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) | |||
| { | |||
| if (index == n) | |||
| { | |||
| deviceId = i; | |||
| name = String (mc.szPname, (size_t) numElementsInArray (mc.szPname)); | |||
| break; | |||
| } | |||
| ++n; | |||
| } | |||
| } | |||
| ScopedPointer <MidiInput> in (new MidiInput (name)); | |||
| ScopedPointer <MidiInCollector> collector (new MidiInCollector (in, *callback)); | |||
| HMIDIIN h; | |||
| MMRESULT err = midiInOpen (&h, deviceId, | |||
| (DWORD_PTR) &MidiInCollector::midiInCallback, | |||
| (DWORD_PTR) (MidiInCollector*) collector, | |||
| CALLBACK_FUNCTION); | |||
| if (err == MMSYSERR_NOERROR) | |||
| { | |||
| collector->deviceHandle = h; | |||
| in->internal = collector.release(); | |||
| return in.release(); | |||
| } | |||
| return nullptr; | |||
| } | |||
| MidiInput::MidiInput (const String& name_) | |||
| : name (name_), | |||
| internal (0) | |||
| { | |||
| } | |||
| MidiInput::~MidiInput() | |||
| { | |||
| delete static_cast<MidiInCollector*> (internal); | |||
| } | |||
| void MidiInput::start() { static_cast<MidiInCollector*> (internal)->start(); } | |||
| void MidiInput::stop() { static_cast<MidiInCollector*> (internal)->stop(); } | |||
| //============================================================================== | |||
| struct MidiOutHandle | |||
| { | |||
| int refCount; | |||
| UINT deviceId; | |||
| HMIDIOUT handle; | |||
| static Array<MidiOutHandle*> activeHandles; | |||
| private: | |||
| JUCE_LEAK_DETECTOR (MidiOutHandle) | |||
| }; | |||
| Array<MidiOutHandle*> MidiOutHandle::activeHandles; | |||
| //============================================================================== | |||
| StringArray MidiOutput::getDevices() | |||
| { | |||
| StringArray s; | |||
| const UINT num = midiOutGetNumDevs(); | |||
| for (UINT i = 0; i < num; ++i) | |||
| { | |||
| MIDIOUTCAPS mc = { 0 }; | |||
| if (midiOutGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) | |||
| s.add (String (mc.szPname, sizeof (mc.szPname))); | |||
| } | |||
| return s; | |||
| } | |||
| int MidiOutput::getDefaultDeviceIndex() | |||
| { | |||
| const UINT num = midiOutGetNumDevs(); | |||
| int n = 0; | |||
| for (UINT i = 0; i < num; ++i) | |||
| { | |||
| MIDIOUTCAPS mc = { 0 }; | |||
| if (midiOutGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) | |||
| { | |||
| if ((mc.wTechnology & MOD_MAPPER) != 0) | |||
| return n; | |||
| ++n; | |||
| } | |||
| } | |||
| return 0; | |||
| } | |||
| MidiOutput* MidiOutput::openDevice (int index) | |||
| { | |||
| UINT deviceId = MIDI_MAPPER; | |||
| const UINT num = midiOutGetNumDevs(); | |||
| int n = 0; | |||
| for (UINT i = 0; i < num; ++i) | |||
| { | |||
| MIDIOUTCAPS mc = { 0 }; | |||
| if (midiOutGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) | |||
| { | |||
| // use the microsoft sw synth as a default - best not to allow deviceId | |||
| // to be MIDI_MAPPER, or else device sharing breaks | |||
| if (String (mc.szPname, sizeof (mc.szPname)).containsIgnoreCase ("microsoft")) | |||
| deviceId = i; | |||
| if (index == n) | |||
| { | |||
| deviceId = i; | |||
| break; | |||
| } | |||
| ++n; | |||
| } | |||
| } | |||
| for (int i = MidiOutHandle::activeHandles.size(); --i >= 0;) | |||
| { | |||
| MidiOutHandle* const han = MidiOutHandle::activeHandles.getUnchecked(i); | |||
| if (han->deviceId == deviceId) | |||
| { | |||
| han->refCount++; | |||
| MidiOutput* const out = new MidiOutput(); | |||
| out->internal = han; | |||
| return out; | |||
| } | |||
| } | |||
| for (int i = 4; --i >= 0;) | |||
| { | |||
| HMIDIOUT h = 0; | |||
| MMRESULT res = midiOutOpen (&h, deviceId, 0, 0, CALLBACK_NULL); | |||
| if (res == MMSYSERR_NOERROR) | |||
| { | |||
| MidiOutHandle* const han = new MidiOutHandle(); | |||
| han->deviceId = deviceId; | |||
| han->refCount = 1; | |||
| han->handle = h; | |||
| MidiOutHandle::activeHandles.add (han); | |||
| MidiOutput* const out = new MidiOutput(); | |||
| out->internal = han; | |||
| return out; | |||
| } | |||
| else if (res == MMSYSERR_ALLOCATED) | |||
| { | |||
| Sleep (100); | |||
| } | |||
| else | |||
| { | |||
| break; | |||
| } | |||
| } | |||
| return nullptr; | |||
| } | |||
| MidiOutput::~MidiOutput() | |||
| { | |||
| stopBackgroundThread(); | |||
| MidiOutHandle* const h = static_cast<MidiOutHandle*> (internal); | |||
| if (MidiOutHandle::activeHandles.contains (h) && --(h->refCount) == 0) | |||
| { | |||
| midiOutClose (h->handle); | |||
| MidiOutHandle::activeHandles.removeFirstMatchingValue (h); | |||
| delete h; | |||
| } | |||
| } | |||
| void MidiOutput::sendMessageNow (const MidiMessage& message) | |||
| { | |||
| const MidiOutHandle* const handle = static_cast<const MidiOutHandle*> (internal); | |||
| if (message.getRawDataSize() > 3 || message.isSysEx()) | |||
| { | |||
| MIDIHDR h = { 0 }; | |||
| h.lpData = (char*) message.getRawData(); | |||
| h.dwBytesRecorded = h.dwBufferLength = (DWORD) message.getRawDataSize(); | |||
| if (midiOutPrepareHeader (handle->handle, &h, sizeof (MIDIHDR)) == MMSYSERR_NOERROR) | |||
| { | |||
| MMRESULT res = midiOutLongMsg (handle->handle, &h, sizeof (MIDIHDR)); | |||
| if (res == MMSYSERR_NOERROR) | |||
| { | |||
| while ((h.dwFlags & MHDR_DONE) == 0) | |||
| Sleep (1); | |||
| int count = 500; // 1 sec timeout | |||
| while (--count >= 0) | |||
| { | |||
| res = midiOutUnprepareHeader (handle->handle, &h, sizeof (MIDIHDR)); | |||
| if (res == MIDIERR_STILLPLAYING) | |||
| Sleep (2); | |||
| else | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| else | |||
| { | |||
| for (int i = 0; i < 50; ++i) | |||
| { | |||
| if (midiOutShortMsg (handle->handle, *(unsigned int*) message.getRawData()) != MIDIERR_NOTREADY) | |||
| break; | |||
| Sleep (1); | |||
| } | |||
| } | |||
| } | |||
| @@ -1,182 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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. | |||
| ============================================================================== | |||
| */ | |||
| AudioSourcePlayer::AudioSourcePlayer() | |||
| : source (nullptr), | |||
| sampleRate (0), | |||
| bufferSize (0), | |||
| lastGain (1.0f), | |||
| gain (1.0f) | |||
| { | |||
| } | |||
| AudioSourcePlayer::~AudioSourcePlayer() | |||
| { | |||
| setSource (nullptr); | |||
| } | |||
| void AudioSourcePlayer::setSource (AudioSource* newSource) | |||
| { | |||
| if (source != newSource) | |||
| { | |||
| AudioSource* const oldSource = source; | |||
| if (newSource != nullptr && bufferSize > 0 && sampleRate > 0) | |||
| newSource->prepareToPlay (bufferSize, sampleRate); | |||
| { | |||
| const ScopedLock sl (readLock); | |||
| source = newSource; | |||
| } | |||
| if (oldSource != nullptr) | |||
| oldSource->releaseResources(); | |||
| } | |||
| } | |||
| void AudioSourcePlayer::setGain (const float newGain) noexcept | |||
| { | |||
| gain = newGain; | |||
| } | |||
| void AudioSourcePlayer::audioDeviceIOCallback (const float** inputChannelData, | |||
| int totalNumInputChannels, | |||
| float** outputChannelData, | |||
| int totalNumOutputChannels, | |||
| int numSamples) | |||
| { | |||
| // these should have been prepared by audioDeviceAboutToStart()... | |||
| jassert (sampleRate > 0 && bufferSize > 0); | |||
| const ScopedLock sl (readLock); | |||
| if (source != nullptr) | |||
| { | |||
| int numActiveChans = 0, numInputs = 0, numOutputs = 0; | |||
| // messy stuff needed to compact the channels down into an array | |||
| // of non-zero pointers.. | |||
| for (int i = 0; i < totalNumInputChannels; ++i) | |||
| { | |||
| if (inputChannelData[i] != nullptr) | |||
| { | |||
| inputChans [numInputs++] = inputChannelData[i]; | |||
| if (numInputs >= numElementsInArray (inputChans)) | |||
| break; | |||
| } | |||
| } | |||
| for (int i = 0; i < totalNumOutputChannels; ++i) | |||
| { | |||
| if (outputChannelData[i] != nullptr) | |||
| { | |||
| outputChans [numOutputs++] = outputChannelData[i]; | |||
| if (numOutputs >= numElementsInArray (outputChans)) | |||
| break; | |||
| } | |||
| } | |||
| if (numInputs > numOutputs) | |||
| { | |||
| // if there aren't enough output channels for the number of | |||
| // inputs, we need to create some temporary extra ones (can't | |||
| // use the input data in case it gets written to) | |||
| tempBuffer.setSize (numInputs - numOutputs, numSamples, | |||
| false, false, true); | |||
| for (int i = 0; i < numOutputs; ++i) | |||
| { | |||
| channels[numActiveChans] = outputChans[i]; | |||
| memcpy (channels[numActiveChans], inputChans[i], sizeof (float) * (size_t) numSamples); | |||
| ++numActiveChans; | |||
| } | |||
| for (int i = numOutputs; i < numInputs; ++i) | |||
| { | |||
| channels[numActiveChans] = tempBuffer.getWritePointer (i - numOutputs); | |||
| memcpy (channels[numActiveChans], inputChans[i], sizeof (float) * (size_t) numSamples); | |||
| ++numActiveChans; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| for (int i = 0; i < numInputs; ++i) | |||
| { | |||
| channels[numActiveChans] = outputChans[i]; | |||
| memcpy (channels[numActiveChans], inputChans[i], sizeof (float) * (size_t) numSamples); | |||
| ++numActiveChans; | |||
| } | |||
| for (int i = numInputs; i < numOutputs; ++i) | |||
| { | |||
| channels[numActiveChans] = outputChans[i]; | |||
| zeromem (channels[numActiveChans], sizeof (float) * (size_t) numSamples); | |||
| ++numActiveChans; | |||
| } | |||
| } | |||
| AudioSampleBuffer buffer (channels, numActiveChans, numSamples); | |||
| AudioSourceChannelInfo info (&buffer, 0, numSamples); | |||
| source->getNextAudioBlock (info); | |||
| for (int i = info.buffer->getNumChannels(); --i >= 0;) | |||
| buffer.applyGainRamp (i, info.startSample, info.numSamples, lastGain, gain); | |||
| lastGain = gain; | |||
| } | |||
| else | |||
| { | |||
| for (int i = 0; i < totalNumOutputChannels; ++i) | |||
| if (outputChannelData[i] != nullptr) | |||
| zeromem (outputChannelData[i], sizeof (float) * (size_t) numSamples); | |||
| } | |||
| } | |||
| void AudioSourcePlayer::audioDeviceAboutToStart (AudioIODevice* device) | |||
| { | |||
| prepareToPlay (device->getCurrentSampleRate(), | |||
| device->getCurrentBufferSizeSamples()); | |||
| } | |||
| void AudioSourcePlayer::prepareToPlay (double newSampleRate, int newBufferSize) | |||
| { | |||
| sampleRate = newSampleRate; | |||
| bufferSize = newBufferSize; | |||
| zeromem (channels, sizeof (channels)); | |||
| if (source != nullptr) | |||
| source->prepareToPlay (bufferSize, sampleRate); | |||
| } | |||
| void AudioSourcePlayer::audioDeviceStopped() | |||
| { | |||
| if (source != nullptr) | |||
| source->releaseResources(); | |||
| sampleRate = 0.0; | |||
| bufferSize = 0; | |||
| tempBuffer.setSize (2, 8); | |||
| } | |||
| @@ -1,115 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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_AUDIOSOURCEPLAYER_H_INCLUDED | |||
| #define JUCE_AUDIOSOURCEPLAYER_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| Wrapper class to continuously stream audio from an audio source to an | |||
| AudioIODevice. | |||
| This object acts as an AudioIODeviceCallback, so can be attached to an | |||
| output device, and will stream audio from an AudioSource. | |||
| */ | |||
| class JUCE_API AudioSourcePlayer : public AudioIODeviceCallback | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates an empty AudioSourcePlayer. */ | |||
| AudioSourcePlayer(); | |||
| /** Destructor. | |||
| Make sure this object isn't still being used by an AudioIODevice before | |||
| deleting it! | |||
| */ | |||
| virtual ~AudioSourcePlayer(); | |||
| //============================================================================== | |||
| /** Changes the current audio source to play from. | |||
| If the source passed in is already being used, this method will do nothing. | |||
| If the source is not null, its prepareToPlay() method will be called | |||
| before it starts being used for playback. | |||
| If there's another source currently playing, its releaseResources() method | |||
| will be called after it has been swapped for the new one. | |||
| @param newSource the new source to use - this will NOT be deleted | |||
| by this object when no longer needed, so it's the | |||
| caller's responsibility to manage it. | |||
| */ | |||
| void setSource (AudioSource* newSource); | |||
| /** Returns the source that's playing. | |||
| May return nullptr if there's no source. | |||
| */ | |||
| AudioSource* getCurrentSource() const noexcept { return source; } | |||
| /** Sets a gain to apply to the audio data. | |||
| @see getGain | |||
| */ | |||
| void setGain (float newGain) noexcept; | |||
| /** Returns the current gain. | |||
| @see setGain | |||
| */ | |||
| float getGain() const noexcept { return gain; } | |||
| //============================================================================== | |||
| /** Implementation of the AudioIODeviceCallback method. */ | |||
| void audioDeviceIOCallback (const float** inputChannelData, | |||
| int totalNumInputChannels, | |||
| float** outputChannelData, | |||
| int totalNumOutputChannels, | |||
| int numSamples) override; | |||
| /** Implementation of the AudioIODeviceCallback method. */ | |||
| void audioDeviceAboutToStart (AudioIODevice* device) override; | |||
| /** Implementation of the AudioIODeviceCallback method. */ | |||
| void audioDeviceStopped() override; | |||
| /** An alternative method for initialising the source without an AudioIODevice. */ | |||
| void prepareToPlay (double sampleRate, int blockSize); | |||
| private: | |||
| //============================================================================== | |||
| CriticalSection readLock; | |||
| AudioSource* source; | |||
| double sampleRate; | |||
| int bufferSize; | |||
| float* channels [128]; | |||
| float* outputChans [128]; | |||
| const float* inputChans [128]; | |||
| AudioSampleBuffer tempBuffer; | |||
| float lastGain, gain; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSourcePlayer) | |||
| }; | |||
| #endif // JUCE_AUDIOSOURCEPLAYER_H_INCLUDED | |||
| @@ -1,298 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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. | |||
| ============================================================================== | |||
| */ | |||
| AudioTransportSource::AudioTransportSource() | |||
| : source (nullptr), | |||
| resamplerSource (nullptr), | |||
| bufferingSource (nullptr), | |||
| positionableSource (nullptr), | |||
| masterSource (nullptr), | |||
| gain (1.0f), | |||
| lastGain (1.0f), | |||
| playing (false), | |||
| stopped (true), | |||
| sampleRate (44100.0), | |||
| sourceSampleRate (0.0), | |||
| blockSize (128), | |||
| readAheadBufferSize (0), | |||
| isPrepared (false), | |||
| inputStreamEOF (false) | |||
| { | |||
| } | |||
| AudioTransportSource::~AudioTransportSource() | |||
| { | |||
| setSource (nullptr); | |||
| releaseMasterResources(); | |||
| } | |||
| void AudioTransportSource::setSource (PositionableAudioSource* const newSource, | |||
| int readAheadSize, TimeSliceThread* readAheadThread, | |||
| double sourceSampleRateToCorrectFor, int maxNumChannels) | |||
| { | |||
| if (source == newSource) | |||
| { | |||
| if (source == nullptr) | |||
| return; | |||
| setSource (nullptr, 0, nullptr); // deselect and reselect to avoid releasing resources wrongly | |||
| } | |||
| readAheadBufferSize = readAheadSize; | |||
| sourceSampleRate = sourceSampleRateToCorrectFor; | |||
| ResamplingAudioSource* newResamplerSource = nullptr; | |||
| BufferingAudioSource* newBufferingSource = nullptr; | |||
| PositionableAudioSource* newPositionableSource = nullptr; | |||
| AudioSource* newMasterSource = nullptr; | |||
| ScopedPointer<ResamplingAudioSource> oldResamplerSource (resamplerSource); | |||
| ScopedPointer<BufferingAudioSource> oldBufferingSource (bufferingSource); | |||
| AudioSource* oldMasterSource = masterSource; | |||
| if (newSource != nullptr) | |||
| { | |||
| newPositionableSource = newSource; | |||
| if (readAheadSize > 0) | |||
| { | |||
| // If you want to use a read-ahead buffer, you must also provide a TimeSliceThread | |||
| // for it to use! | |||
| jassert (readAheadThread != nullptr); | |||
| newPositionableSource = newBufferingSource | |||
| = new BufferingAudioSource (newPositionableSource, *readAheadThread, | |||
| false, readAheadSize, maxNumChannels); | |||
| } | |||
| newPositionableSource->setNextReadPosition (0); | |||
| if (sourceSampleRateToCorrectFor > 0) | |||
| newMasterSource = newResamplerSource | |||
| = new ResamplingAudioSource (newPositionableSource, false, maxNumChannels); | |||
| else | |||
| newMasterSource = newPositionableSource; | |||
| if (isPrepared) | |||
| { | |||
| if (newResamplerSource != nullptr && sourceSampleRate > 0 && sampleRate > 0) | |||
| newResamplerSource->setResamplingRatio (sourceSampleRate / sampleRate); | |||
| newMasterSource->prepareToPlay (blockSize, sampleRate); | |||
| } | |||
| } | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| source = newSource; | |||
| resamplerSource = newResamplerSource; | |||
| bufferingSource = newBufferingSource; | |||
| masterSource = newMasterSource; | |||
| positionableSource = newPositionableSource; | |||
| inputStreamEOF = false; | |||
| playing = false; | |||
| } | |||
| if (oldMasterSource != nullptr) | |||
| oldMasterSource->releaseResources(); | |||
| } | |||
| void AudioTransportSource::start() | |||
| { | |||
| if ((! playing) && masterSource != nullptr) | |||
| { | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| playing = true; | |||
| stopped = false; | |||
| inputStreamEOF = false; | |||
| } | |||
| sendChangeMessage(); | |||
| } | |||
| } | |||
| void AudioTransportSource::stop() | |||
| { | |||
| if (playing) | |||
| { | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| playing = false; | |||
| } | |||
| int n = 500; | |||
| while (--n >= 0 && ! stopped) | |||
| Thread::sleep (2); | |||
| sendChangeMessage(); | |||
| } | |||
| } | |||
| void AudioTransportSource::setPosition (double newPosition) | |||
| { | |||
| if (sampleRate > 0.0) | |||
| setNextReadPosition ((int64) (newPosition * sampleRate)); | |||
| } | |||
| double AudioTransportSource::getCurrentPosition() const | |||
| { | |||
| if (sampleRate > 0.0) | |||
| return getNextReadPosition() / sampleRate; | |||
| return 0.0; | |||
| } | |||
| double AudioTransportSource::getLengthInSeconds() const | |||
| { | |||
| if (sampleRate > 0.0) | |||
| return getTotalLength() / sampleRate; | |||
| return 0.0; | |||
| } | |||
| void AudioTransportSource::setNextReadPosition (int64 newPosition) | |||
| { | |||
| if (positionableSource != nullptr) | |||
| { | |||
| if (sampleRate > 0 && sourceSampleRate > 0) | |||
| newPosition = (int64) (newPosition * sourceSampleRate / sampleRate); | |||
| positionableSource->setNextReadPosition (newPosition); | |||
| if (resamplerSource != nullptr) | |||
| resamplerSource->flushBuffers(); | |||
| inputStreamEOF = false; | |||
| } | |||
| } | |||
| int64 AudioTransportSource::getNextReadPosition() const | |||
| { | |||
| if (positionableSource != nullptr) | |||
| { | |||
| const double ratio = (sampleRate > 0 && sourceSampleRate > 0) ? sampleRate / sourceSampleRate : 1.0; | |||
| return (int64) (positionableSource->getNextReadPosition() * ratio); | |||
| } | |||
| return 0; | |||
| } | |||
| int64 AudioTransportSource::getTotalLength() const | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| if (positionableSource != nullptr) | |||
| { | |||
| const double ratio = (sampleRate > 0 && sourceSampleRate > 0) ? sampleRate / sourceSampleRate : 1.0; | |||
| return (int64) (positionableSource->getTotalLength() * ratio); | |||
| } | |||
| return 0; | |||
| } | |||
| bool AudioTransportSource::isLooping() const | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| return positionableSource != nullptr && positionableSource->isLooping(); | |||
| } | |||
| void AudioTransportSource::setGain (const float newGain) noexcept | |||
| { | |||
| gain = newGain; | |||
| } | |||
| void AudioTransportSource::prepareToPlay (int samplesPerBlockExpected, double newSampleRate) | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| sampleRate = newSampleRate; | |||
| blockSize = samplesPerBlockExpected; | |||
| if (masterSource != nullptr) | |||
| masterSource->prepareToPlay (samplesPerBlockExpected, sampleRate); | |||
| if (resamplerSource != nullptr && sourceSampleRate > 0) | |||
| resamplerSource->setResamplingRatio (sourceSampleRate / sampleRate); | |||
| inputStreamEOF = false; | |||
| isPrepared = true; | |||
| } | |||
| void AudioTransportSource::releaseMasterResources() | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| if (masterSource != nullptr) | |||
| masterSource->releaseResources(); | |||
| isPrepared = false; | |||
| } | |||
| void AudioTransportSource::releaseResources() | |||
| { | |||
| releaseMasterResources(); | |||
| } | |||
| void AudioTransportSource::getNextAudioBlock (const AudioSourceChannelInfo& info) | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| if (masterSource != nullptr && ! stopped) | |||
| { | |||
| masterSource->getNextAudioBlock (info); | |||
| if (! playing) | |||
| { | |||
| // just stopped playing, so fade out the last block.. | |||
| for (int i = info.buffer->getNumChannels(); --i >= 0;) | |||
| info.buffer->applyGainRamp (i, info.startSample, jmin (256, info.numSamples), 1.0f, 0.0f); | |||
| if (info.numSamples > 256) | |||
| info.buffer->clear (info.startSample + 256, info.numSamples - 256); | |||
| } | |||
| if (positionableSource->getNextReadPosition() > positionableSource->getTotalLength() + 1 | |||
| && ! positionableSource->isLooping()) | |||
| { | |||
| playing = false; | |||
| inputStreamEOF = true; | |||
| sendChangeMessage(); | |||
| } | |||
| stopped = ! playing; | |||
| for (int i = info.buffer->getNumChannels(); --i >= 0;) | |||
| info.buffer->applyGainRamp (i, info.startSample, info.numSamples, lastGain, gain); | |||
| } | |||
| else | |||
| { | |||
| info.clearActiveBufferRegion(); | |||
| stopped = true; | |||
| } | |||
| lastGain = gain; | |||
| } | |||
| @@ -1,180 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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_AUDIOTRANSPORTSOURCE_H_INCLUDED | |||
| #define JUCE_AUDIOTRANSPORTSOURCE_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| An AudioSource that takes a PositionableAudioSource and allows it to be | |||
| played, stopped, started, etc. | |||
| This can also be told use a buffer and background thread to read ahead, and | |||
| if can correct for different sample-rates. | |||
| You may want to use one of these along with an AudioSourcePlayer and AudioIODevice | |||
| to control playback of an audio file. | |||
| @see AudioSource, AudioSourcePlayer | |||
| */ | |||
| class JUCE_API AudioTransportSource : public PositionableAudioSource, | |||
| public ChangeBroadcaster | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates an AudioTransportSource. | |||
| After creating one of these, use the setSource() method to select an input source. | |||
| */ | |||
| AudioTransportSource(); | |||
| /** Destructor. */ | |||
| ~AudioTransportSource(); | |||
| //============================================================================== | |||
| /** Sets the reader that is being used as the input source. | |||
| This will stop playback, reset the position to 0 and change to the new reader. | |||
| The source passed in will not be deleted by this object, so must be managed by | |||
| the caller. | |||
| @param newSource the new input source to use. This may be zero | |||
| @param readAheadBufferSize a size of buffer to use for reading ahead. If this | |||
| is zero, no reading ahead will be done; if it's | |||
| greater than zero, a BufferingAudioSource will be used | |||
| to do the reading-ahead. If you set a non-zero value here, | |||
| you'll also need to set the readAheadThread parameter. | |||
| @param readAheadThread if you set readAheadBufferSize to a non-zero value, then | |||
| you'll also need to supply this TimeSliceThread object for | |||
| the background reader to use. The thread object must not be | |||
| deleted while the AudioTransport source is still using it. | |||
| @param sourceSampleRateToCorrectFor if this is non-zero, it specifies the sample | |||
| rate of the source, and playback will be sample-rate | |||
| adjusted to maintain playback at the correct pitch. If | |||
| this is 0, no sample-rate adjustment will be performed | |||
| @param maxNumChannels the maximum number of channels that may need to be played | |||
| */ | |||
| void setSource (PositionableAudioSource* newSource, | |||
| int readAheadBufferSize = 0, | |||
| TimeSliceThread* readAheadThread = nullptr, | |||
| double sourceSampleRateToCorrectFor = 0.0, | |||
| int maxNumChannels = 2); | |||
| //============================================================================== | |||
| /** Changes the current playback position in the source stream. | |||
| The next time the getNextAudioBlock() method is called, this | |||
| is the time from which it'll read data. | |||
| @see getPosition | |||
| */ | |||
| void setPosition (double newPosition); | |||
| /** Returns the position that the next data block will be read from | |||
| This is a time in seconds. | |||
| */ | |||
| double getCurrentPosition() const; | |||
| /** Returns the stream's length in seconds. */ | |||
| double getLengthInSeconds() const; | |||
| /** Returns true if the player has stopped because its input stream ran out of data. */ | |||
| bool hasStreamFinished() const noexcept { return inputStreamEOF; } | |||
| //============================================================================== | |||
| /** Starts playing (if a source has been selected). | |||
| If it starts playing, this will send a message to any ChangeListeners | |||
| that are registered with this object. | |||
| */ | |||
| void start(); | |||
| /** Stops playing. | |||
| If it's actually playing, this will send a message to any ChangeListeners | |||
| that are registered with this object. | |||
| */ | |||
| void stop(); | |||
| /** Returns true if it's currently playing. */ | |||
| bool isPlaying() const noexcept { return playing; } | |||
| //============================================================================== | |||
| /** Changes the gain to apply to the output. | |||
| @param newGain a factor by which to multiply the outgoing samples, | |||
| so 1.0 = 0dB, 0.5 = -6dB, 2.0 = 6dB, etc. | |||
| */ | |||
| void setGain (float newGain) noexcept; | |||
| /** Returns the current gain setting. | |||
| @see setGain | |||
| */ | |||
| float getGain() const noexcept { return gain; } | |||
| //============================================================================== | |||
| /** 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&) 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; | |||
| /** Implements the PositionableAudioSource method. */ | |||
| bool isLooping() const override; | |||
| private: | |||
| //============================================================================== | |||
| PositionableAudioSource* source; | |||
| ResamplingAudioSource* resamplerSource; | |||
| BufferingAudioSource* bufferingSource; | |||
| PositionableAudioSource* positionableSource; | |||
| AudioSource* masterSource; | |||
| CriticalSection callbackLock; | |||
| float volatile gain, lastGain; | |||
| bool volatile playing, stopped; | |||
| double sampleRate, sourceSampleRate; | |||
| int blockSize, readAheadBufferSize; | |||
| bool volatile isPrepared, inputStreamEOF; | |||
| void releaseMasterResources(); | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioTransportSource) | |||
| }; | |||
| #endif // JUCE_AUDIOTRANSPORTSOURCE_H_INCLUDED | |||
| @@ -1,954 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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. | |||
| ============================================================================== | |||
| */ | |||
| static const char* const aiffFormatName = "AIFF file"; | |||
| //============================================================================== | |||
| const char* const AiffAudioFormat::appleOneShot = "apple one shot"; | |||
| const char* const AiffAudioFormat::appleRootSet = "apple root set"; | |||
| const char* const AiffAudioFormat::appleRootNote = "apple root note"; | |||
| const char* const AiffAudioFormat::appleBeats = "apple beats"; | |||
| const char* const AiffAudioFormat::appleDenominator = "apple denominator"; | |||
| const char* const AiffAudioFormat::appleNumerator = "apple numerator"; | |||
| const char* const AiffAudioFormat::appleTag = "apple tag"; | |||
| const char* const AiffAudioFormat::appleKey = "apple key"; | |||
| //============================================================================== | |||
| namespace AiffFileHelpers | |||
| { | |||
| inline int chunkName (const char* name) noexcept { return (int) ByteOrder::littleEndianInt (name); } | |||
| #if JUCE_MSVC | |||
| #pragma pack (push, 1) | |||
| #endif | |||
| //============================================================================== | |||
| struct InstChunk | |||
| { | |||
| struct Loop | |||
| { | |||
| uint16 type; // these are different in AIFF and WAV | |||
| uint16 startIdentifier; | |||
| uint16 endIdentifier; | |||
| } JUCE_PACKED; | |||
| int8 baseNote; | |||
| int8 detune; | |||
| int8 lowNote; | |||
| int8 highNote; | |||
| int8 lowVelocity; | |||
| int8 highVelocity; | |||
| int16 gain; | |||
| Loop sustainLoop; | |||
| Loop releaseLoop; | |||
| void copyTo (StringPairArray& values) const | |||
| { | |||
| values.set ("MidiUnityNote", String (baseNote)); | |||
| values.set ("Detune", String (detune)); | |||
| values.set ("LowNote", String (lowNote)); | |||
| values.set ("HighNote", String (highNote)); | |||
| values.set ("LowVelocity", String (lowVelocity)); | |||
| values.set ("HighVelocity", String (highVelocity)); | |||
| values.set ("Gain", String ((int16) ByteOrder::swapIfLittleEndian ((uint16) gain))); | |||
| values.set ("NumSampleLoops", String (2)); // always 2 with AIFF, WAV can have more | |||
| values.set ("Loop0Type", String (ByteOrder::swapIfLittleEndian (sustainLoop.type))); | |||
| values.set ("Loop0StartIdentifier", String (ByteOrder::swapIfLittleEndian (sustainLoop.startIdentifier))); | |||
| values.set ("Loop0EndIdentifier", String (ByteOrder::swapIfLittleEndian (sustainLoop.endIdentifier))); | |||
| values.set ("Loop1Type", String (ByteOrder::swapIfLittleEndian (releaseLoop.type))); | |||
| values.set ("Loop1StartIdentifier", String (ByteOrder::swapIfLittleEndian (releaseLoop.startIdentifier))); | |||
| values.set ("Loop1EndIdentifier", String (ByteOrder::swapIfLittleEndian (releaseLoop.endIdentifier))); | |||
| } | |||
| static uint16 getValue16 (const StringPairArray& values, const char* name, const char* def) | |||
| { | |||
| return ByteOrder::swapIfLittleEndian ((uint16) values.getValue (name, def).getIntValue()); | |||
| } | |||
| static int8 getValue8 (const StringPairArray& values, const char* name, const char* def) | |||
| { | |||
| return (int8) values.getValue (name, def).getIntValue(); | |||
| } | |||
| static void create (MemoryBlock& block, const StringPairArray& values) | |||
| { | |||
| if (values.getAllKeys().contains ("MidiUnityNote", true)) | |||
| { | |||
| block.setSize ((sizeof (InstChunk) + 3) & ~(size_t) 3, true); | |||
| InstChunk& inst = *static_cast<InstChunk*> (block.getData()); | |||
| inst.baseNote = getValue8 (values, "MidiUnityNote", "60"); | |||
| inst.detune = getValue8 (values, "Detune", "0"); | |||
| inst.lowNote = getValue8 (values, "LowNote", "0"); | |||
| inst.highNote = getValue8 (values, "HighNote", "127"); | |||
| inst.lowVelocity = getValue8 (values, "LowVelocity", "1"); | |||
| inst.highVelocity = getValue8 (values, "HighVelocity", "127"); | |||
| inst.gain = (int16) getValue16 (values, "Gain", "0"); | |||
| inst.sustainLoop.type = getValue16 (values, "Loop0Type", "0"); | |||
| inst.sustainLoop.startIdentifier = getValue16 (values, "Loop0StartIdentifier", "0"); | |||
| inst.sustainLoop.endIdentifier = getValue16 (values, "Loop0EndIdentifier", "0"); | |||
| inst.releaseLoop.type = getValue16 (values, "Loop1Type", "0"); | |||
| inst.releaseLoop.startIdentifier = getValue16 (values, "Loop1StartIdentifier", "0"); | |||
| inst.releaseLoop.endIdentifier = getValue16 (values, "Loop1EndIdentifier", "0"); | |||
| } | |||
| } | |||
| } JUCE_PACKED; | |||
| //============================================================================== | |||
| struct BASCChunk | |||
| { | |||
| enum Key | |||
| { | |||
| minor = 1, | |||
| major = 2, | |||
| neither = 3, | |||
| both = 4 | |||
| }; | |||
| BASCChunk (InputStream& input) | |||
| { | |||
| zerostruct (*this); | |||
| flags = (uint32) input.readIntBigEndian(); | |||
| numBeats = (uint32) input.readIntBigEndian(); | |||
| rootNote = (uint16) input.readShortBigEndian(); | |||
| key = (uint16) input.readShortBigEndian(); | |||
| timeSigNum = (uint16) input.readShortBigEndian(); | |||
| timeSigDen = (uint16) input.readShortBigEndian(); | |||
| oneShot = (uint16) input.readShortBigEndian(); | |||
| input.read (unknown, sizeof (unknown)); | |||
| } | |||
| void addToMetadata (StringPairArray& metadata) const | |||
| { | |||
| const bool rootNoteSet = rootNote != 0; | |||
| setBoolFlag (metadata, AiffAudioFormat::appleOneShot, oneShot == 2); | |||
| setBoolFlag (metadata, AiffAudioFormat::appleRootSet, rootNoteSet); | |||
| if (rootNoteSet) | |||
| metadata.set (AiffAudioFormat::appleRootNote, String (rootNote)); | |||
| metadata.set (AiffAudioFormat::appleBeats, String (numBeats)); | |||
| metadata.set (AiffAudioFormat::appleDenominator, String (timeSigDen)); | |||
| metadata.set (AiffAudioFormat::appleNumerator, String (timeSigNum)); | |||
| const char* keyString = nullptr; | |||
| switch (key) | |||
| { | |||
| case minor: keyString = "major"; break; | |||
| case major: keyString = "major"; break; | |||
| case neither: keyString = "neither"; break; | |||
| case both: keyString = "both"; break; | |||
| } | |||
| if (keyString != nullptr) | |||
| metadata.set (AiffAudioFormat::appleKey, keyString); | |||
| } | |||
| void setBoolFlag (StringPairArray& values, const char* name, bool shouldBeSet) const | |||
| { | |||
| values.set (name, shouldBeSet ? "1" : "0"); | |||
| } | |||
| uint32 flags; | |||
| uint32 numBeats; | |||
| uint16 rootNote; | |||
| uint16 key; | |||
| uint16 timeSigNum; | |||
| uint16 timeSigDen; | |||
| uint16 oneShot; | |||
| uint8 unknown[66]; | |||
| } JUCE_PACKED; | |||
| #if JUCE_MSVC | |||
| #pragma pack (pop) | |||
| #endif | |||
| //============================================================================== | |||
| static String readCATEChunk (InputStream& input, const uint32 length) | |||
| { | |||
| MemoryBlock mb; | |||
| input.skipNextBytes (4); | |||
| input.readIntoMemoryBlock (mb, (ssize_t) length - 4); | |||
| static const char* appleGenres[] = | |||
| { | |||
| "Rock/Blues", | |||
| "Electronic/Dance", | |||
| "Jazz", | |||
| "Urban", | |||
| "World/Ethnic", | |||
| "Cinematic/New Age", | |||
| "Orchestral", | |||
| "Country/Folk", | |||
| "Experimental", | |||
| "Other Genre", | |||
| nullptr | |||
| }; | |||
| const StringArray genres (appleGenres); | |||
| StringArray tagsArray; | |||
| int bytesLeft = (int) mb.getSize(); | |||
| const char* data = static_cast<const char*> (mb.getData()); | |||
| while (bytesLeft > 0) | |||
| { | |||
| const String tag (CharPointer_UTF8 (data), | |||
| CharPointer_UTF8 (data + bytesLeft)); | |||
| if (tag.isNotEmpty()) | |||
| tagsArray.add (data); | |||
| const int numBytesInTag = genres.contains (tag) ? 118 : 50; | |||
| data += numBytesInTag; | |||
| bytesLeft -= numBytesInTag; | |||
| } | |||
| return tagsArray.joinIntoString (";"); | |||
| } | |||
| //============================================================================== | |||
| namespace MarkChunk | |||
| { | |||
| static bool metaDataContainsZeroIdentifiers (const StringPairArray& values) | |||
| { | |||
| // (zero cue identifiers are valid for WAV but not for AIFF) | |||
| const String cueString ("Cue"); | |||
| const String noteString ("CueNote"); | |||
| const String identifierString ("Identifier"); | |||
| const StringArray& keys = values.getAllKeys(); | |||
| for (int i = 0; i < keys.size(); ++i) | |||
| { | |||
| const String key (keys[i]); | |||
| if (key.startsWith (noteString)) | |||
| continue; // zero identifier IS valid in a COMT chunk | |||
| if (key.startsWith (cueString) && key.contains (identifierString)) | |||
| { | |||
| const int value = values.getValue (key, "-1").getIntValue(); | |||
| if (value == 0) | |||
| return true; | |||
| } | |||
| } | |||
| return false; | |||
| } | |||
| static void create (MemoryBlock& block, const StringPairArray& values) | |||
| { | |||
| const int numCues = values.getValue ("NumCuePoints", "0").getIntValue(); | |||
| if (numCues > 0) | |||
| { | |||
| MemoryOutputStream out (block, false); | |||
| out.writeShortBigEndian ((short) numCues); | |||
| const int numCueLabels = values.getValue ("NumCueLabels", "0").getIntValue(); | |||
| const int idOffset = metaDataContainsZeroIdentifiers (values) ? 1 : 0; // can't have zero IDs in AIFF | |||
| #if JUCE_DEBUG | |||
| Array<int> identifiers; | |||
| #endif | |||
| for (int i = 0; i < numCues; ++i) | |||
| { | |||
| const String prefixCue ("Cue" + String (i)); | |||
| const int identifier = idOffset + values.getValue (prefixCue + "Identifier", "1").getIntValue(); | |||
| #if JUCE_DEBUG | |||
| jassert (! identifiers.contains (identifier)); | |||
| identifiers.add (identifier); | |||
| #endif | |||
| const int offset = values.getValue (prefixCue + "Offset", "0").getIntValue(); | |||
| String label ("CueLabel" + String (i)); | |||
| for (int labelIndex = 0; labelIndex < numCueLabels; ++labelIndex) | |||
| { | |||
| const String prefixLabel ("CueLabel" + String (labelIndex)); | |||
| const int labelIdentifier = idOffset + values.getValue (prefixLabel + "Identifier", "1").getIntValue(); | |||
| if (labelIdentifier == identifier) | |||
| { | |||
| label = values.getValue (prefixLabel + "Text", label); | |||
| break; | |||
| } | |||
| } | |||
| out.writeShortBigEndian ((short) identifier); | |||
| out.writeIntBigEndian (offset); | |||
| const size_t labelLength = jmin ((size_t) 254, label.getNumBytesAsUTF8()); // seems to need null terminator even though it's a pstring | |||
| out.writeByte ((char) labelLength + 1); | |||
| out.write (label.toUTF8(), labelLength); | |||
| out.writeByte (0); | |||
| } | |||
| if ((out.getDataSize() & 1) != 0) | |||
| out.writeByte (0); | |||
| } | |||
| } | |||
| } | |||
| //============================================================================== | |||
| namespace COMTChunk | |||
| { | |||
| static void create (MemoryBlock& block, const StringPairArray& values) | |||
| { | |||
| const int numNotes = values.getValue ("NumCueNotes", "0").getIntValue(); | |||
| if (numNotes > 0) | |||
| { | |||
| MemoryOutputStream out (block, false); | |||
| out.writeShortBigEndian ((short) numNotes); | |||
| for (int i = 0; i < numNotes; ++i) | |||
| { | |||
| const String prefix ("CueNote" + String (i)); | |||
| out.writeIntBigEndian (values.getValue (prefix + "TimeStamp", "0").getIntValue()); | |||
| out.writeShortBigEndian ((short) values.getValue (prefix + "Identifier", "0").getIntValue()); | |||
| const String comment (values.getValue (prefix + "Text", String())); | |||
| const size_t commentLength = jmin (comment.getNumBytesAsUTF8(), (size_t) 65534); | |||
| out.writeShortBigEndian ((short) commentLength + 1); | |||
| out.write (comment.toUTF8(), commentLength); | |||
| out.writeByte (0); | |||
| if ((out.getDataSize() & 1) != 0) | |||
| out.writeByte (0); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| //============================================================================== | |||
| class AiffAudioFormatReader : public AudioFormatReader | |||
| { | |||
| public: | |||
| AiffAudioFormatReader (InputStream* in) | |||
| : AudioFormatReader (in, aiffFormatName) | |||
| { | |||
| using namespace AiffFileHelpers; | |||
| if (input->readInt() == chunkName ("FORM")) | |||
| { | |||
| const int len = input->readIntBigEndian(); | |||
| const int64 end = input->getPosition() + len; | |||
| const int nextType = input->readInt(); | |||
| if (nextType == chunkName ("AIFF") || nextType == chunkName ("AIFC")) | |||
| { | |||
| bool hasGotVer = false; | |||
| bool hasGotData = false; | |||
| bool hasGotType = false; | |||
| while (input->getPosition() < end) | |||
| { | |||
| const int type = input->readInt(); | |||
| const uint32 length = (uint32) input->readIntBigEndian(); | |||
| const int64 chunkEnd = input->getPosition() + length; | |||
| if (type == chunkName ("FVER")) | |||
| { | |||
| hasGotVer = true; | |||
| const int ver = input->readIntBigEndian(); | |||
| if (ver != 0 && ver != (int) 0xa2805140) | |||
| break; | |||
| } | |||
| else if (type == chunkName ("COMM")) | |||
| { | |||
| hasGotType = true; | |||
| numChannels = (unsigned int) input->readShortBigEndian(); | |||
| lengthInSamples = input->readIntBigEndian(); | |||
| bitsPerSample = (unsigned int) input->readShortBigEndian(); | |||
| bytesPerFrame = (int) ((numChannels * bitsPerSample) >> 3); | |||
| unsigned char sampleRateBytes[10]; | |||
| input->read (sampleRateBytes, 10); | |||
| const int byte0 = sampleRateBytes[0]; | |||
| if ((byte0 & 0x80) != 0 | |||
| || byte0 <= 0x3F || byte0 > 0x40 | |||
| || (byte0 == 0x40 && sampleRateBytes[1] > 0x1C)) | |||
| break; | |||
| unsigned int sampRate = ByteOrder::bigEndianInt (sampleRateBytes + 2); | |||
| sampRate >>= (16414 - ByteOrder::bigEndianShort (sampleRateBytes)); | |||
| sampleRate = (int) sampRate; | |||
| if (length <= 18) | |||
| { | |||
| // some types don't have a chunk large enough to include a compression | |||
| // type, so assume it's just big-endian pcm | |||
| littleEndian = false; | |||
| } | |||
| else | |||
| { | |||
| const int compType = input->readInt(); | |||
| if (compType == chunkName ("NONE") || compType == chunkName ("twos")) | |||
| { | |||
| littleEndian = false; | |||
| } | |||
| else if (compType == chunkName ("sowt")) | |||
| { | |||
| littleEndian = true; | |||
| } | |||
| else if (compType == chunkName ("fl32") || compType == chunkName ("FL32")) | |||
| { | |||
| littleEndian = false; | |||
| usesFloatingPointData = true; | |||
| } | |||
| else | |||
| { | |||
| sampleRate = 0; | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| else if (type == chunkName ("SSND")) | |||
| { | |||
| hasGotData = true; | |||
| const int offset = input->readIntBigEndian(); | |||
| dataChunkStart = input->getPosition() + 4 + offset; | |||
| lengthInSamples = (bytesPerFrame > 0) ? jmin (lengthInSamples, ((int64) length) / (int64) bytesPerFrame) : 0; | |||
| } | |||
| else if (type == chunkName ("MARK")) | |||
| { | |||
| const uint16 numCues = (uint16) input->readShortBigEndian(); | |||
| // these two are always the same for AIFF-read files | |||
| metadataValues.set ("NumCuePoints", String (numCues)); | |||
| metadataValues.set ("NumCueLabels", String (numCues)); | |||
| for (uint16 i = 0; i < numCues; ++i) | |||
| { | |||
| uint16 identifier = (uint16) input->readShortBigEndian(); | |||
| uint32 offset = (uint32) input->readIntBigEndian(); | |||
| uint8 stringLength = (uint8) input->readByte(); | |||
| MemoryBlock textBlock; | |||
| input->readIntoMemoryBlock (textBlock, stringLength); | |||
| // if the stringLength is even then read one more byte as the | |||
| // string needs to be an even number of bytes INCLUDING the | |||
| // leading length character in the pascal string | |||
| if ((stringLength & 1) == 0) | |||
| input->readByte(); | |||
| const String prefixCue ("Cue" + String (i)); | |||
| metadataValues.set (prefixCue + "Identifier", String (identifier)); | |||
| metadataValues.set (prefixCue + "Offset", String (offset)); | |||
| const String prefixLabel ("CueLabel" + String (i)); | |||
| metadataValues.set (prefixLabel + "Identifier", String (identifier)); | |||
| metadataValues.set (prefixLabel + "Text", textBlock.toString()); | |||
| } | |||
| } | |||
| else if (type == chunkName ("COMT")) | |||
| { | |||
| const uint16 numNotes = (uint16) input->readShortBigEndian(); | |||
| metadataValues.set ("NumCueNotes", String (numNotes)); | |||
| for (uint16 i = 0; i < numNotes; ++i) | |||
| { | |||
| uint32 timestamp = (uint32) input->readIntBigEndian(); | |||
| uint16 identifier = (uint16) input->readShortBigEndian(); // may be zero in this case | |||
| uint16 stringLength = (uint16) input->readShortBigEndian(); | |||
| MemoryBlock textBlock; | |||
| input->readIntoMemoryBlock (textBlock, stringLength + (stringLength & 1)); | |||
| const String prefix ("CueNote" + String (i)); | |||
| metadataValues.set (prefix + "TimeStamp", String (timestamp)); | |||
| metadataValues.set (prefix + "Identifier", String (identifier)); | |||
| metadataValues.set (prefix + "Text", textBlock.toString()); | |||
| } | |||
| } | |||
| else if (type == chunkName ("INST")) | |||
| { | |||
| HeapBlock <InstChunk> inst; | |||
| inst.calloc (jmax ((size_t) length + 1, sizeof (InstChunk)), 1); | |||
| input->read (inst, (int) length); | |||
| inst->copyTo (metadataValues); | |||
| } | |||
| else if (type == chunkName ("basc")) | |||
| { | |||
| AiffFileHelpers::BASCChunk (*input).addToMetadata (metadataValues); | |||
| } | |||
| else if (type == chunkName ("cate")) | |||
| { | |||
| metadataValues.set (AiffAudioFormat::appleTag, | |||
| AiffFileHelpers::readCATEChunk (*input, length));; | |||
| } | |||
| else if ((hasGotVer && hasGotData && hasGotType) | |||
| || chunkEnd < input->getPosition() | |||
| || input->isExhausted()) | |||
| { | |||
| break; | |||
| } | |||
| input->setPosition (chunkEnd + (chunkEnd & 1)); // (chunks should be aligned to an even byte address) | |||
| } | |||
| } | |||
| } | |||
| if (metadataValues.size() > 0) | |||
| metadataValues.set ("MetaDataSource", "AIFF"); | |||
| } | |||
| //============================================================================== | |||
| bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | |||
| int64 startSampleInFile, int numSamples) override | |||
| { | |||
| clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer, | |||
| startSampleInFile, numSamples, lengthInSamples); | |||
| if (numSamples <= 0) | |||
| return true; | |||
| input->setPosition (dataChunkStart + startSampleInFile * bytesPerFrame); | |||
| while (numSamples > 0) | |||
| { | |||
| const int tempBufSize = 480 * 3 * 4; // (keep this a multiple of 3) | |||
| char tempBuffer [tempBufSize]; | |||
| const int numThisTime = jmin (tempBufSize / bytesPerFrame, numSamples); | |||
| const int bytesRead = input->read (tempBuffer, numThisTime * bytesPerFrame); | |||
| if (bytesRead < numThisTime * bytesPerFrame) | |||
| { | |||
| jassert (bytesRead >= 0); | |||
| zeromem (tempBuffer + bytesRead, (size_t) (numThisTime * bytesPerFrame - bytesRead)); | |||
| } | |||
| if (littleEndian) | |||
| copySampleData<AudioData::LittleEndian> (bitsPerSample, usesFloatingPointData, | |||
| destSamples, startOffsetInDestBuffer, numDestChannels, | |||
| tempBuffer, (int) numChannels, numThisTime); | |||
| else | |||
| copySampleData<AudioData::BigEndian> (bitsPerSample, usesFloatingPointData, | |||
| destSamples, startOffsetInDestBuffer, numDestChannels, | |||
| tempBuffer, (int) numChannels, numThisTime); | |||
| startOffsetInDestBuffer += numThisTime; | |||
| numSamples -= numThisTime; | |||
| } | |||
| return true; | |||
| } | |||
| template <typename Endianness> | |||
| static void copySampleData (unsigned int bitsPerSample, const bool usesFloatingPointData, | |||
| int* const* destSamples, int startOffsetInDestBuffer, int numDestChannels, | |||
| const void* sourceData, int numChannels, int numSamples) noexcept | |||
| { | |||
| switch (bitsPerSample) | |||
| { | |||
| case 8: ReadHelper<AudioData::Int32, AudioData::Int8, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break; | |||
| case 16: ReadHelper<AudioData::Int32, AudioData::Int16, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break; | |||
| case 24: ReadHelper<AudioData::Int32, AudioData::Int24, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break; | |||
| case 32: if (usesFloatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); | |||
| else ReadHelper<AudioData::Int32, AudioData::Int32, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break; | |||
| default: jassertfalse; break; | |||
| } | |||
| } | |||
| int bytesPerFrame; | |||
| int64 dataChunkStart; | |||
| bool littleEndian; | |||
| private: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AiffAudioFormatReader) | |||
| }; | |||
| //============================================================================== | |||
| class AiffAudioFormatWriter : public AudioFormatWriter | |||
| { | |||
| public: | |||
| AiffAudioFormatWriter (OutputStream* out, double rate, | |||
| unsigned int numChans, unsigned int bits, | |||
| const StringPairArray& metadataValues) | |||
| : AudioFormatWriter (out, aiffFormatName, rate, numChans, bits), | |||
| lengthInSamples (0), | |||
| bytesWritten (0), | |||
| writeFailed (false) | |||
| { | |||
| using namespace AiffFileHelpers; | |||
| if (metadataValues.size() > 0) | |||
| { | |||
| // The meta data should have been santised for the AIFF format. | |||
| // If it was originally sourced from a WAV file the MetaDataSource | |||
| // key should be removed (or set to "AIFF") once this has been done | |||
| jassert (metadataValues.getValue ("MetaDataSource", "None") != "WAV"); | |||
| MarkChunk::create (markChunk, metadataValues); | |||
| COMTChunk::create (comtChunk, metadataValues); | |||
| InstChunk::create (instChunk, metadataValues); | |||
| } | |||
| headerPosition = out->getPosition(); | |||
| writeHeader(); | |||
| } | |||
| ~AiffAudioFormatWriter() | |||
| { | |||
| if ((bytesWritten & 1) != 0) | |||
| output->writeByte (0); | |||
| writeHeader(); | |||
| } | |||
| //============================================================================== | |||
| bool write (const int** data, int numSamples) override | |||
| { | |||
| jassert (data != nullptr && *data != nullptr); // the input must contain at least one channel! | |||
| if (writeFailed) | |||
| return false; | |||
| const size_t bytes = (size_t) numSamples * numChannels * bitsPerSample / 8; | |||
| tempBlock.ensureSize ((size_t) bytes, false); | |||
| switch (bitsPerSample) | |||
| { | |||
| case 8: WriteHelper<AudioData::Int8, AudioData::Int32, AudioData::BigEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples); break; | |||
| case 16: WriteHelper<AudioData::Int16, AudioData::Int32, AudioData::BigEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples); break; | |||
| case 24: WriteHelper<AudioData::Int24, AudioData::Int32, AudioData::BigEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples); break; | |||
| case 32: WriteHelper<AudioData::Int32, AudioData::Int32, AudioData::BigEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples); break; | |||
| default: jassertfalse; break; | |||
| } | |||
| if (bytesWritten + bytes >= (size_t) 0xfff00000 | |||
| || ! output->write (tempBlock.getData(), bytes)) | |||
| { | |||
| // failed to write to disk, so let's try writing the header. | |||
| // If it's just run out of disk space, then if it does manage | |||
| // to write the header, we'll still have a useable file.. | |||
| writeHeader(); | |||
| writeFailed = true; | |||
| return false; | |||
| } | |||
| else | |||
| { | |||
| bytesWritten += bytes; | |||
| lengthInSamples += (uint64) numSamples; | |||
| return true; | |||
| } | |||
| } | |||
| private: | |||
| MemoryBlock tempBlock, markChunk, comtChunk, instChunk; | |||
| uint64 lengthInSamples, bytesWritten; | |||
| int64 headerPosition; | |||
| bool writeFailed; | |||
| void writeHeader() | |||
| { | |||
| using namespace AiffFileHelpers; | |||
| const bool couldSeekOk = output->setPosition (headerPosition); | |||
| (void) couldSeekOk; | |||
| // if this fails, you've given it an output stream that can't seek! It needs | |||
| // to be able to seek back to write the header | |||
| jassert (couldSeekOk); | |||
| const int headerLen = (int) (54 + (markChunk.getSize() > 0 ? markChunk.getSize() + 8 : 0) | |||
| + (comtChunk.getSize() > 0 ? comtChunk.getSize() + 8 : 0) | |||
| + (instChunk.getSize() > 0 ? instChunk.getSize() + 8 : 0)); | |||
| int audioBytes = (int) (lengthInSamples * ((bitsPerSample * numChannels) / 8)); | |||
| audioBytes += (audioBytes & 1); | |||
| output->writeInt (chunkName ("FORM")); | |||
| output->writeIntBigEndian (headerLen + audioBytes - 8); | |||
| output->writeInt (chunkName ("AIFF")); | |||
| output->writeInt (chunkName ("COMM")); | |||
| output->writeIntBigEndian (18); | |||
| output->writeShortBigEndian ((short) numChannels); | |||
| output->writeIntBigEndian ((int) lengthInSamples); | |||
| output->writeShortBigEndian ((short) bitsPerSample); | |||
| uint8 sampleRateBytes[10] = { 0 }; | |||
| if (sampleRate <= 1) | |||
| { | |||
| sampleRateBytes[0] = 0x3f; | |||
| sampleRateBytes[1] = 0xff; | |||
| sampleRateBytes[2] = 0x80; | |||
| } | |||
| else | |||
| { | |||
| int mask = 0x40000000; | |||
| sampleRateBytes[0] = 0x40; | |||
| if (sampleRate >= mask) | |||
| { | |||
| jassertfalse; | |||
| sampleRateBytes[1] = 0x1d; | |||
| } | |||
| else | |||
| { | |||
| int n = (int) sampleRate; | |||
| int i; | |||
| for (i = 0; i <= 32 ; ++i) | |||
| { | |||
| if ((n & mask) != 0) | |||
| break; | |||
| mask >>= 1; | |||
| } | |||
| n = n << (i + 1); | |||
| sampleRateBytes[1] = (uint8) (29 - i); | |||
| sampleRateBytes[2] = (uint8) ((n >> 24) & 0xff); | |||
| sampleRateBytes[3] = (uint8) ((n >> 16) & 0xff); | |||
| sampleRateBytes[4] = (uint8) ((n >> 8) & 0xff); | |||
| sampleRateBytes[5] = (uint8) (n & 0xff); | |||
| } | |||
| } | |||
| output->write (sampleRateBytes, 10); | |||
| if (markChunk.getSize() > 0) | |||
| { | |||
| output->writeInt (chunkName ("MARK")); | |||
| output->writeIntBigEndian ((int) markChunk.getSize()); | |||
| *output << markChunk; | |||
| } | |||
| if (comtChunk.getSize() > 0) | |||
| { | |||
| output->writeInt (chunkName ("COMT")); | |||
| output->writeIntBigEndian ((int) comtChunk.getSize()); | |||
| *output << comtChunk; | |||
| } | |||
| if (instChunk.getSize() > 0) | |||
| { | |||
| output->writeInt (chunkName ("INST")); | |||
| output->writeIntBigEndian ((int) instChunk.getSize()); | |||
| *output << instChunk; | |||
| } | |||
| output->writeInt (chunkName ("SSND")); | |||
| output->writeIntBigEndian (audioBytes + 8); | |||
| output->writeInt (0); | |||
| output->writeInt (0); | |||
| jassert (output->getPosition() == headerLen); | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AiffAudioFormatWriter) | |||
| }; | |||
| //============================================================================== | |||
| class MemoryMappedAiffReader : public MemoryMappedAudioFormatReader | |||
| { | |||
| public: | |||
| MemoryMappedAiffReader (const File& f, const AiffAudioFormatReader& reader) | |||
| : MemoryMappedAudioFormatReader (f, reader, reader.dataChunkStart, | |||
| reader.bytesPerFrame * reader.lengthInSamples, reader.bytesPerFrame), | |||
| littleEndian (reader.littleEndian) | |||
| { | |||
| } | |||
| bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | |||
| int64 startSampleInFile, int numSamples) override | |||
| { | |||
| clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer, | |||
| startSampleInFile, numSamples, lengthInSamples); | |||
| if (map == nullptr || ! mappedSection.contains (Range<int64> (startSampleInFile, startSampleInFile + numSamples))) | |||
| { | |||
| jassertfalse; // you must make sure that the window contains all the samples you're going to attempt to read. | |||
| return false; | |||
| } | |||
| if (littleEndian) | |||
| AiffAudioFormatReader::copySampleData<AudioData::LittleEndian> | |||
| (bitsPerSample, usesFloatingPointData, destSamples, startOffsetInDestBuffer, | |||
| numDestChannels, sampleToPointer (startSampleInFile), (int) numChannels, numSamples); | |||
| else | |||
| AiffAudioFormatReader::copySampleData<AudioData::BigEndian> | |||
| (bitsPerSample, usesFloatingPointData, destSamples, startOffsetInDestBuffer, | |||
| numDestChannels, sampleToPointer (startSampleInFile), (int) numChannels, numSamples); | |||
| return true; | |||
| } | |||
| void readMaxLevels (int64 startSampleInFile, int64 numSamples, | |||
| float& min0, float& max0, float& min1, float& max1) | |||
| { | |||
| if (numSamples <= 0) | |||
| { | |||
| min0 = max0 = min1 = max1 = 0; | |||
| return; | |||
| } | |||
| if (map == nullptr || ! mappedSection.contains (Range<int64> (startSampleInFile, startSampleInFile + numSamples))) | |||
| { | |||
| jassertfalse; // you must make sure that the window contains all the samples you're going to attempt to read. | |||
| min0 = max0 = min1 = max1 = 0; | |||
| return; | |||
| } | |||
| switch (bitsPerSample) | |||
| { | |||
| case 8: scanMinAndMax<AudioData::UInt8> (startSampleInFile, numSamples, min0, max0, min1, max1); break; | |||
| case 16: scanMinAndMax<AudioData::Int16> (startSampleInFile, numSamples, min0, max0, min1, max1); break; | |||
| case 24: scanMinAndMax<AudioData::Int24> (startSampleInFile, numSamples, min0, max0, min1, max1); break; | |||
| case 32: if (usesFloatingPointData) scanMinAndMax<AudioData::Float32> (startSampleInFile, numSamples, min0, max0, min1, max1); | |||
| else scanMinAndMax<AudioData::Int32> (startSampleInFile, numSamples, min0, max0, min1, max1); break; | |||
| default: jassertfalse; break; | |||
| } | |||
| } | |||
| private: | |||
| const bool littleEndian; | |||
| template <typename SampleType> | |||
| void scanMinAndMax (int64 startSampleInFile, int64 numSamples, | |||
| float& min0, float& max0, float& min1, float& max1) const noexcept | |||
| { | |||
| scanMinAndMax2<SampleType> (0, startSampleInFile, numSamples, min0, max0); | |||
| if (numChannels > 1) | |||
| scanMinAndMax2<SampleType> (1, startSampleInFile, numSamples, min1, max1); | |||
| else | |||
| min1 = max1 = 0; | |||
| } | |||
| template <typename SampleType> | |||
| void scanMinAndMax2 (int channel, int64 startSampleInFile, int64 numSamples, float& mn, float& mx) const noexcept | |||
| { | |||
| if (littleEndian) | |||
| scanMinAndMaxInterleaved<SampleType, AudioData::LittleEndian> (channel, startSampleInFile, numSamples, mn, mx); | |||
| else | |||
| scanMinAndMaxInterleaved<SampleType, AudioData::BigEndian> (channel, startSampleInFile, numSamples, mn, mx); | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedAiffReader) | |||
| }; | |||
| //============================================================================== | |||
| AiffAudioFormat::AiffAudioFormat() : AudioFormat (aiffFormatName, ".aiff .aif") | |||
| { | |||
| } | |||
| AiffAudioFormat::~AiffAudioFormat() | |||
| { | |||
| } | |||
| Array<int> AiffAudioFormat::getPossibleSampleRates() | |||
| { | |||
| const int rates[] = { 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000, 0 }; | |||
| return Array<int> (rates); | |||
| } | |||
| Array<int> AiffAudioFormat::getPossibleBitDepths() | |||
| { | |||
| const int depths[] = { 8, 16, 24, 0 }; | |||
| return Array<int> (depths); | |||
| } | |||
| bool AiffAudioFormat::canDoStereo() { return true; } | |||
| bool AiffAudioFormat::canDoMono() { return true; } | |||
| #if JUCE_MAC | |||
| bool AiffAudioFormat::canHandleFile (const File& f) | |||
| { | |||
| if (AudioFormat::canHandleFile (f)) | |||
| return true; | |||
| const OSType type = f.getMacOSType(); | |||
| // (NB: written as hex to avoid four-char-constant warnings) | |||
| return type == 0x41494646 /* AIFF */ || type == 0x41494643 /* AIFC */ | |||
| || type == 0x61696666 /* aiff */ || type == 0x61696663 /* aifc */; | |||
| } | |||
| #endif | |||
| AudioFormatReader* AiffAudioFormat::createReaderFor (InputStream* sourceStream, const bool deleteStreamIfOpeningFails) | |||
| { | |||
| ScopedPointer <AiffAudioFormatReader> w (new AiffAudioFormatReader (sourceStream)); | |||
| if (w->sampleRate > 0 && w->numChannels > 0) | |||
| return w.release(); | |||
| if (! deleteStreamIfOpeningFails) | |||
| w->input = nullptr; | |||
| return nullptr; | |||
| } | |||
| MemoryMappedAudioFormatReader* AiffAudioFormat::createMemoryMappedReader (const File& file) | |||
| { | |||
| if (FileInputStream* fin = file.createInputStream()) | |||
| { | |||
| AiffAudioFormatReader reader (fin); | |||
| if (reader.lengthInSamples > 0) | |||
| return new MemoryMappedAiffReader (file, reader); | |||
| } | |||
| return nullptr; | |||
| } | |||
| AudioFormatWriter* AiffAudioFormat::createWriterFor (OutputStream* out, | |||
| double sampleRate, | |||
| unsigned int numberOfChannels, | |||
| int bitsPerSample, | |||
| const StringPairArray& metadataValues, | |||
| int /*qualityOptionIndex*/) | |||
| { | |||
| if (getPossibleBitDepths().contains (bitsPerSample)) | |||
| return new AiffAudioFormatWriter (out, sampleRate, numberOfChannels, (unsigned int) bitsPerSample, metadataValues); | |||
| return nullptr; | |||
| } | |||
| @@ -1,84 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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. | |||
| ============================================================================== | |||
| */ | |||
| //============================================================================== | |||
| /** | |||
| Reads and Writes AIFF format audio files. | |||
| @see AudioFormat | |||
| */ | |||
| class JUCE_API AiffAudioFormat : public AudioFormat | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates an format object. */ | |||
| AiffAudioFormat(); | |||
| /** Destructor. */ | |||
| ~AiffAudioFormat(); | |||
| //============================================================================== | |||
| /** Metadata property name used when reading a aiff file with a basc chunk. */ | |||
| static const char* const appleOneShot; | |||
| /** Metadata property name used when reading a aiff file with a basc chunk. */ | |||
| static const char* const appleRootSet; | |||
| /** Metadata property name used when reading a aiff file with a basc chunk. */ | |||
| static const char* const appleRootNote; | |||
| /** Metadata property name used when reading a aiff file with a basc chunk. */ | |||
| static const char* const appleBeats; | |||
| /** Metadata property name used when reading a aiff file with a basc chunk. */ | |||
| static const char* const appleDenominator; | |||
| /** Metadata property name used when reading a aiff file with a basc chunk. */ | |||
| static const char* const appleNumerator; | |||
| /** Metadata property name used when reading a aiff file with a basc chunk. */ | |||
| static const char* const appleTag; | |||
| /** Metadata property name used when reading a aiff file with a basc chunk. */ | |||
| static const char* const appleKey; | |||
| //============================================================================== | |||
| Array<int> getPossibleSampleRates() override; | |||
| Array<int> getPossibleBitDepths() override; | |||
| bool canDoStereo() override; | |||
| bool canDoMono() override; | |||
| #if JUCE_MAC | |||
| bool canHandleFile (const File& fileToTest) override; | |||
| #endif | |||
| //============================================================================== | |||
| AudioFormatReader* createReaderFor (InputStream* sourceStream, | |||
| bool deleteStreamIfOpeningFails) override; | |||
| MemoryMappedAudioFormatReader* createMemoryMappedReader (const File&) override; | |||
| AudioFormatWriter* createWriterFor (OutputStream* streamToWriteTo, | |||
| double sampleRateToUse, | |||
| unsigned int numberOfChannels, | |||
| int bitsPerSample, | |||
| const StringPairArray& metadataValues, | |||
| int qualityOptionIndex) override; | |||
| private: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AiffAudioFormat) | |||
| }; | |||
| @@ -1,528 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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_MAC || JUCE_IOS | |||
| //============================================================================== | |||
| namespace | |||
| { | |||
| const char* const coreAudioFormatName = "CoreAudio supported file"; | |||
| StringArray findFileExtensionsForCoreAudioCodecs() | |||
| { | |||
| StringArray extensionsArray; | |||
| CFArrayRef extensions = nullptr; | |||
| UInt32 sizeOfArray = sizeof (extensions); | |||
| if (AudioFileGetGlobalInfo (kAudioFileGlobalInfo_AllExtensions, 0, 0, &sizeOfArray, &extensions) == noErr) | |||
| { | |||
| const CFIndex numValues = CFArrayGetCount (extensions); | |||
| for (CFIndex i = 0; i < numValues; ++i) | |||
| extensionsArray.add ("." + String::fromCFString ((CFStringRef) CFArrayGetValueAtIndex (extensions, i))); | |||
| CFRelease (extensions); | |||
| } | |||
| return extensionsArray; | |||
| } | |||
| } | |||
| //============================================================================== | |||
| const char* const CoreAudioFormat::midiDataBase64 = "midiDataBase64"; | |||
| const char* const CoreAudioFormat::tempo = "tempo"; | |||
| const char* const CoreAudioFormat::timeSig = "time signature"; | |||
| const char* const CoreAudioFormat::keySig = "key signature"; | |||
| //============================================================================== | |||
| struct CoreAudioFormatMetatdata | |||
| { | |||
| static uint32 chunkName (const char* const name) noexcept { return ByteOrder::bigEndianInt (name); } | |||
| //============================================================================== | |||
| struct FileHeader | |||
| { | |||
| FileHeader (InputStream& input) | |||
| { | |||
| fileType = (uint32) input.readIntBigEndian(); | |||
| fileVersion = (uint16) input.readShortBigEndian(); | |||
| fileFlags = (uint16) input.readShortBigEndian(); | |||
| } | |||
| uint32 fileType; | |||
| uint16 fileVersion; | |||
| uint16 fileFlags; | |||
| }; | |||
| //============================================================================== | |||
| struct ChunkHeader | |||
| { | |||
| ChunkHeader (InputStream& input) | |||
| { | |||
| chunkType = (uint32) input.readIntBigEndian(); | |||
| chunkSize = (int64) input.readInt64BigEndian(); | |||
| } | |||
| uint32 chunkType; | |||
| int64 chunkSize; | |||
| }; | |||
| //============================================================================== | |||
| struct AudioDescriptionChunk | |||
| { | |||
| AudioDescriptionChunk (InputStream& input) | |||
| { | |||
| sampleRate = input.readDoubleBigEndian(); | |||
| formatID = (uint32) input.readIntBigEndian(); | |||
| formatFlags = (uint32) input.readIntBigEndian(); | |||
| bytesPerPacket = (uint32) input.readIntBigEndian(); | |||
| framesPerPacket = (uint32) input.readIntBigEndian(); | |||
| channelsPerFrame = (uint32) input.readIntBigEndian(); | |||
| bitsPerChannel = (uint32) input.readIntBigEndian(); | |||
| } | |||
| double sampleRate; | |||
| uint32 formatID; | |||
| uint32 formatFlags; | |||
| uint32 bytesPerPacket; | |||
| uint32 framesPerPacket; | |||
| uint32 channelsPerFrame; | |||
| uint32 bitsPerChannel; | |||
| }; | |||
| //============================================================================== | |||
| struct UserDefinedChunk | |||
| { | |||
| UserDefinedChunk (InputStream& input, int64 size) | |||
| { | |||
| // a user defined chunk contains 16 bytes of a UUID first | |||
| uuid[1] = input.readInt64BigEndian(); | |||
| uuid[0] = input.readInt64BigEndian(); | |||
| input.skipNextBytes (size - 16); | |||
| } | |||
| int64 uuid[2]; | |||
| }; | |||
| //============================================================================== | |||
| static StringPairArray parseMidiChunk (InputStream& input, int64 size) | |||
| { | |||
| const int64 originalPosition = input.getPosition(); | |||
| MemoryBlock midiBlock; | |||
| input.readIntoMemoryBlock (midiBlock, (ssize_t) size); | |||
| MemoryInputStream midiInputStream (midiBlock, false); | |||
| StringPairArray midiMetadata; | |||
| MidiFile midiFile; | |||
| if (midiFile.readFrom (midiInputStream)) | |||
| { | |||
| midiMetadata.set (CoreAudioFormat::midiDataBase64, midiBlock.toBase64Encoding()); | |||
| findTempoEvents (midiFile, midiMetadata); | |||
| findTimeSigEvents (midiFile, midiMetadata); | |||
| findKeySigEvents (midiFile, midiMetadata); | |||
| } | |||
| input.setPosition (originalPosition + size); | |||
| return midiMetadata; | |||
| } | |||
| static void findTempoEvents (MidiFile& midiFile, StringPairArray& midiMetadata) | |||
| { | |||
| MidiMessageSequence tempoEvents; | |||
| midiFile.findAllTempoEvents (tempoEvents); | |||
| const int numTempoEvents = tempoEvents.getNumEvents(); | |||
| MemoryOutputStream tempoSequence; | |||
| for (int i = 0; i < numTempoEvents; ++i) | |||
| { | |||
| const double tempo = getTempoFromTempoMetaEvent (tempoEvents.getEventPointer (i)); | |||
| if (tempo > 0.0) | |||
| { | |||
| if (i == 0) | |||
| midiMetadata.set (CoreAudioFormat::tempo, String (tempo)); | |||
| if (numTempoEvents > 1) | |||
| tempoSequence << String (tempo) << ',' << tempoEvents.getEventTime (i) << ';'; | |||
| } | |||
| } | |||
| if (tempoSequence.getDataSize() > 0) | |||
| midiMetadata.set ("tempo sequence", tempoSequence.toUTF8()); | |||
| } | |||
| static double getTempoFromTempoMetaEvent (MidiMessageSequence::MidiEventHolder* holder) | |||
| { | |||
| if (holder != nullptr) | |||
| { | |||
| const MidiMessage& midiMessage = holder->message; | |||
| if (midiMessage.isTempoMetaEvent()) | |||
| { | |||
| const double tempoSecondsPerQuarterNote = midiMessage.getTempoSecondsPerQuarterNote(); | |||
| if (tempoSecondsPerQuarterNote > 0.0) | |||
| return 60.0 / tempoSecondsPerQuarterNote; | |||
| } | |||
| } | |||
| return 0.0; | |||
| } | |||
| static void findTimeSigEvents (MidiFile& midiFile, StringPairArray& midiMetadata) | |||
| { | |||
| MidiMessageSequence timeSigEvents; | |||
| midiFile.findAllTimeSigEvents (timeSigEvents); | |||
| const int numTimeSigEvents = timeSigEvents.getNumEvents(); | |||
| MemoryOutputStream timeSigSequence; | |||
| for (int i = 0; i < numTimeSigEvents; ++i) | |||
| { | |||
| int numerator, denominator; | |||
| timeSigEvents.getEventPointer(i)->message.getTimeSignatureInfo (numerator, denominator); | |||
| String timeSigString; | |||
| timeSigString << numerator << '/' << denominator; | |||
| if (i == 0) | |||
| midiMetadata.set (CoreAudioFormat::timeSig, timeSigString); | |||
| if (numTimeSigEvents > 1) | |||
| timeSigSequence << timeSigString << ',' << timeSigEvents.getEventTime (i) << ';'; | |||
| } | |||
| if (timeSigSequence.getDataSize() > 0) | |||
| midiMetadata.set ("time signature sequence", timeSigSequence.toUTF8()); | |||
| } | |||
| static void findKeySigEvents (MidiFile& midiFile, StringPairArray& midiMetadata) | |||
| { | |||
| MidiMessageSequence keySigEvents; | |||
| midiFile.findAllKeySigEvents (keySigEvents); | |||
| const int numKeySigEvents = keySigEvents.getNumEvents(); | |||
| MemoryOutputStream keySigSequence; | |||
| for (int i = 0; i < numKeySigEvents; ++i) | |||
| { | |||
| const MidiMessage& message (keySigEvents.getEventPointer (i)->message); | |||
| const int key = jlimit (0, 14, message.getKeySignatureNumberOfSharpsOrFlats() + 7); | |||
| const bool isMajor = message.isKeySignatureMajorKey(); | |||
| static const char* majorKeys[] = { "Cb", "Gb", "Db", "Ab", "Eb", "Bb", "F", "C", "G", "D", "A", "E", "B", "F#", "C#" }; | |||
| static const char* minorKeys[] = { "Ab", "Eb", "Bb", "F", "C", "G", "D", "A", "E", "B", "F#", "C#", "G#", "D#", "A#" }; | |||
| String keySigString (isMajor ? majorKeys[key] | |||
| : minorKeys[key]); | |||
| if (! isMajor) | |||
| keySigString << 'm'; | |||
| if (i == 0) | |||
| midiMetadata.set (CoreAudioFormat::keySig, keySigString); | |||
| if (numKeySigEvents > 1) | |||
| keySigSequence << keySigString << ',' << keySigEvents.getEventTime (i) << ';'; | |||
| } | |||
| if (keySigSequence.getDataSize() > 0) | |||
| midiMetadata.set ("key signature sequence", keySigSequence.toUTF8()); | |||
| } | |||
| //============================================================================== | |||
| static StringPairArray parseInformationChunk (InputStream& input) | |||
| { | |||
| StringPairArray infoStrings; | |||
| const uint32 numEntries = (uint32) input.readIntBigEndian(); | |||
| for (uint32 i = 0; i < numEntries; ++i) | |||
| infoStrings.set (input.readString(), input.readString()); | |||
| return infoStrings; | |||
| } | |||
| //============================================================================== | |||
| static bool read (InputStream& input, StringPairArray& metadataValues) | |||
| { | |||
| const int64 originalPos = input.getPosition(); | |||
| const FileHeader cafFileHeader (input); | |||
| const bool isCafFile = cafFileHeader.fileType == chunkName ("caff"); | |||
| if (isCafFile) | |||
| { | |||
| while (! input.isExhausted()) | |||
| { | |||
| const ChunkHeader chunkHeader (input); | |||
| if (chunkHeader.chunkType == chunkName ("desc")) | |||
| { | |||
| AudioDescriptionChunk audioDescriptionChunk (input); | |||
| } | |||
| else if (chunkHeader.chunkType == chunkName ("uuid")) | |||
| { | |||
| UserDefinedChunk userDefinedChunk (input, chunkHeader.chunkSize); | |||
| } | |||
| else if (chunkHeader.chunkType == chunkName ("data")) | |||
| { | |||
| // -1 signifies an unknown data size so the data has to be at the | |||
| // end of the file so we must have finished the header | |||
| if (chunkHeader.chunkSize == -1) | |||
| break; | |||
| input.skipNextBytes (chunkHeader.chunkSize); | |||
| } | |||
| else if (chunkHeader.chunkType == chunkName ("midi")) | |||
| { | |||
| metadataValues.addArray (parseMidiChunk (input, chunkHeader.chunkSize)); | |||
| } | |||
| else if (chunkHeader.chunkType == chunkName ("info")) | |||
| { | |||
| metadataValues.addArray (parseInformationChunk (input)); | |||
| } | |||
| else | |||
| { | |||
| // we aren't decoding this chunk yet so just skip over it | |||
| input.skipNextBytes (chunkHeader.chunkSize); | |||
| } | |||
| } | |||
| } | |||
| input.setPosition (originalPos); | |||
| return isCafFile; | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| class CoreAudioReader : public AudioFormatReader | |||
| { | |||
| public: | |||
| CoreAudioReader (InputStream* const inp) | |||
| : AudioFormatReader (inp, coreAudioFormatName), | |||
| ok (false), lastReadPosition (0) | |||
| { | |||
| usesFloatingPointData = true; | |||
| bitsPerSample = 32; | |||
| if (input != nullptr) | |||
| CoreAudioFormatMetatdata::read (*input, metadataValues); | |||
| OSStatus status = AudioFileOpenWithCallbacks (this, | |||
| &readCallback, | |||
| nullptr, // write needs to be null to avoid permisisions errors | |||
| &getSizeCallback, | |||
| nullptr, // setSize needs to be null to avoid permisisions errors | |||
| 0, // AudioFileTypeID inFileTypeHint | |||
| &audioFileID); | |||
| if (status == noErr) | |||
| { | |||
| status = ExtAudioFileWrapAudioFileID (audioFileID, false, &audioFileRef); | |||
| if (status == noErr) | |||
| { | |||
| AudioStreamBasicDescription sourceAudioFormat; | |||
| UInt32 audioStreamBasicDescriptionSize = sizeof (AudioStreamBasicDescription); | |||
| ExtAudioFileGetProperty (audioFileRef, | |||
| kExtAudioFileProperty_FileDataFormat, | |||
| &audioStreamBasicDescriptionSize, | |||
| &sourceAudioFormat); | |||
| numChannels = sourceAudioFormat.mChannelsPerFrame; | |||
| sampleRate = sourceAudioFormat.mSampleRate; | |||
| UInt32 sizeOfLengthProperty = sizeof (int64); | |||
| ExtAudioFileGetProperty (audioFileRef, | |||
| kExtAudioFileProperty_FileLengthFrames, | |||
| &sizeOfLengthProperty, | |||
| &lengthInSamples); | |||
| destinationAudioFormat.mSampleRate = sampleRate; | |||
| destinationAudioFormat.mFormatID = kAudioFormatLinearPCM; | |||
| destinationAudioFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat | kLinearPCMFormatFlagIsNonInterleaved | kAudioFormatFlagsNativeEndian; | |||
| destinationAudioFormat.mBitsPerChannel = sizeof (float) * 8; | |||
| destinationAudioFormat.mChannelsPerFrame = numChannels; | |||
| destinationAudioFormat.mBytesPerFrame = sizeof (float); | |||
| destinationAudioFormat.mFramesPerPacket = 1; | |||
| destinationAudioFormat.mBytesPerPacket = destinationAudioFormat.mFramesPerPacket * destinationAudioFormat.mBytesPerFrame; | |||
| status = ExtAudioFileSetProperty (audioFileRef, | |||
| kExtAudioFileProperty_ClientDataFormat, | |||
| sizeof (AudioStreamBasicDescription), | |||
| &destinationAudioFormat); | |||
| if (status == noErr) | |||
| { | |||
| bufferList.malloc (1, sizeof (AudioBufferList) + numChannels * sizeof (AudioBuffer)); | |||
| bufferList->mNumberBuffers = numChannels; | |||
| ok = true; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| ~CoreAudioReader() | |||
| { | |||
| ExtAudioFileDispose (audioFileRef); | |||
| AudioFileClose (audioFileID); | |||
| } | |||
| //============================================================================== | |||
| bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | |||
| int64 startSampleInFile, int numSamples) override | |||
| { | |||
| clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer, | |||
| startSampleInFile, numSamples, lengthInSamples); | |||
| if (numSamples <= 0) | |||
| return true; | |||
| if (lastReadPosition != startSampleInFile) | |||
| { | |||
| OSStatus status = ExtAudioFileSeek (audioFileRef, startSampleInFile); | |||
| if (status != noErr) | |||
| return false; | |||
| lastReadPosition = startSampleInFile; | |||
| } | |||
| while (numSamples > 0) | |||
| { | |||
| const int numThisTime = jmin (8192, numSamples); | |||
| const size_t numBytes = sizeof (float) * (size_t) numThisTime; | |||
| audioDataBlock.ensureSize (numBytes * numChannels, false); | |||
| float* data = static_cast<float*> (audioDataBlock.getData()); | |||
| for (int j = (int) numChannels; --j >= 0;) | |||
| { | |||
| bufferList->mBuffers[j].mNumberChannels = 1; | |||
| bufferList->mBuffers[j].mDataByteSize = (UInt32) numBytes; | |||
| bufferList->mBuffers[j].mData = data; | |||
| data += numThisTime; | |||
| } | |||
| UInt32 numFramesToRead = (UInt32) numThisTime; | |||
| OSStatus status = ExtAudioFileRead (audioFileRef, &numFramesToRead, bufferList); | |||
| if (status != noErr) | |||
| return false; | |||
| for (int i = numDestChannels; --i >= 0;) | |||
| { | |||
| if (destSamples[i] != nullptr) | |||
| { | |||
| if (i < (int) numChannels) | |||
| memcpy (destSamples[i] + startOffsetInDestBuffer, bufferList->mBuffers[i].mData, numBytes); | |||
| else | |||
| zeromem (destSamples[i] + startOffsetInDestBuffer, numBytes); | |||
| } | |||
| } | |||
| startOffsetInDestBuffer += numThisTime; | |||
| numSamples -= numThisTime; | |||
| lastReadPosition += numThisTime; | |||
| } | |||
| return true; | |||
| } | |||
| bool ok; | |||
| private: | |||
| AudioFileID audioFileID; | |||
| ExtAudioFileRef audioFileRef; | |||
| AudioStreamBasicDescription destinationAudioFormat; | |||
| MemoryBlock audioDataBlock; | |||
| HeapBlock<AudioBufferList> bufferList; | |||
| int64 lastReadPosition; | |||
| static SInt64 getSizeCallback (void* inClientData) | |||
| { | |||
| return static_cast<CoreAudioReader*> (inClientData)->input->getTotalLength(); | |||
| } | |||
| static OSStatus readCallback (void* inClientData, | |||
| SInt64 inPosition, | |||
| UInt32 requestCount, | |||
| void* buffer, | |||
| UInt32* actualCount) | |||
| { | |||
| CoreAudioReader* const reader = static_cast<CoreAudioReader*> (inClientData); | |||
| reader->input->setPosition (inPosition); | |||
| *actualCount = (UInt32) reader->input->read (buffer, (int) requestCount); | |||
| return noErr; | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreAudioReader) | |||
| }; | |||
| //============================================================================== | |||
| CoreAudioFormat::CoreAudioFormat() | |||
| : AudioFormat (coreAudioFormatName, findFileExtensionsForCoreAudioCodecs()) | |||
| { | |||
| } | |||
| CoreAudioFormat::~CoreAudioFormat() {} | |||
| Array<int> CoreAudioFormat::getPossibleSampleRates() { return Array<int>(); } | |||
| Array<int> CoreAudioFormat::getPossibleBitDepths() { return Array<int>(); } | |||
| bool CoreAudioFormat::canDoStereo() { return true; } | |||
| bool CoreAudioFormat::canDoMono() { return true; } | |||
| //============================================================================== | |||
| AudioFormatReader* CoreAudioFormat::createReaderFor (InputStream* sourceStream, | |||
| bool deleteStreamIfOpeningFails) | |||
| { | |||
| ScopedPointer<CoreAudioReader> r (new CoreAudioReader (sourceStream)); | |||
| if (r->ok) | |||
| return r.release(); | |||
| if (! deleteStreamIfOpeningFails) | |||
| r->input = nullptr; | |||
| return nullptr; | |||
| } | |||
| AudioFormatWriter* CoreAudioFormat::createWriterFor (OutputStream*, | |||
| double /*sampleRateToUse*/, | |||
| unsigned int /*numberOfChannels*/, | |||
| int /*bitsPerSample*/, | |||
| const StringPairArray& /*metadataValues*/, | |||
| int /*qualityOptionIndex*/) | |||
| { | |||
| jassertfalse; // not yet implemented! | |||
| return nullptr; | |||
| } | |||
| #endif | |||
| @@ -1,77 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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_MAC || JUCE_IOS || DOXYGEN | |||
| //============================================================================== | |||
| /** | |||
| OSX and iOS only - This uses the AudioToolbox framework to read any audio | |||
| format that the system has a codec for. | |||
| This should be able to understand formats such as mp3, m4a, etc. | |||
| @see AudioFormat | |||
| */ | |||
| class JUCE_API CoreAudioFormat : public AudioFormat | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates a format object. */ | |||
| CoreAudioFormat(); | |||
| /** Destructor. */ | |||
| ~CoreAudioFormat(); | |||
| //============================================================================== | |||
| /** Metadata property name used when reading a caf file with a MIDI chunk. */ | |||
| static const char* const midiDataBase64; | |||
| /** Metadata property name used when reading a caf file with tempo information. */ | |||
| static const char* const tempo; | |||
| /** Metadata property name used when reading a caf file time signature information. */ | |||
| static const char* const timeSig; | |||
| /** Metadata property name used when reading a caf file time signature information. */ | |||
| static const char* const keySig; | |||
| //============================================================================== | |||
| Array<int> getPossibleSampleRates() override; | |||
| Array<int> getPossibleBitDepths() override; | |||
| bool canDoStereo() override; | |||
| bool canDoMono() override; | |||
| //============================================================================== | |||
| AudioFormatReader* createReaderFor (InputStream*, | |||
| bool deleteStreamIfOpeningFails) override; | |||
| AudioFormatWriter* createWriterFor (OutputStream*, | |||
| double sampleRateToUse, | |||
| unsigned int numberOfChannels, | |||
| int bitsPerSample, | |||
| const StringPairArray& metadataValues, | |||
| int qualityOptionIndex) override; | |||
| private: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreAudioFormat) | |||
| }; | |||
| #endif | |||
| @@ -1,556 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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_FLAC | |||
| namespace FlacNamespace | |||
| { | |||
| #if JUCE_INCLUDE_FLAC_CODE || ! defined (JUCE_INCLUDE_FLAC_CODE) | |||
| #undef VERSION | |||
| #define VERSION "1.2.1" | |||
| #define FLAC__NO_DLL 1 | |||
| #if JUCE_MSVC | |||
| #pragma warning (disable: 4267 4127 4244 4996 4100 4701 4702 4013 4133 4206 4312 4505 4365 4005 4334 181 111) | |||
| #endif | |||
| #if JUCE_MAC | |||
| #define FLAC__SYS_DARWIN 1 | |||
| #endif | |||
| #ifndef SIZE_MAX | |||
| #define SIZE_MAX 0xffffffff | |||
| #endif | |||
| #if JUCE_CLANG | |||
| #pragma clang diagnostic push | |||
| #pragma clang diagnostic ignored "-Wconversion" | |||
| #pragma clang diagnostic ignored "-Wshadow" | |||
| #pragma clang diagnostic ignored "-Wdeprecated-register" | |||
| #endif | |||
| #if JUCE_INTEL | |||
| #if JUCE_32BIT | |||
| #define FLAC__CPU_IA32 1 | |||
| #endif | |||
| #if JUCE_64BIT | |||
| #define FLAC__CPU_X86_64 1 | |||
| #endif | |||
| #define FLAC__HAS_X86INTRIN 1 | |||
| #endif | |||
| #undef __STDC_LIMIT_MACROS | |||
| #define __STDC_LIMIT_MACROS 1 | |||
| #define flac_max jmax | |||
| #define flac_min jmin | |||
| #include "flac/all.h" | |||
| #include "flac/libFLAC/bitmath.c" | |||
| #include "flac/libFLAC/bitreader.c" | |||
| #include "flac/libFLAC/bitwriter.c" | |||
| #include "flac/libFLAC/cpu.c" | |||
| #include "flac/libFLAC/crc.c" | |||
| #include "flac/libFLAC/fixed.c" | |||
| #include "flac/libFLAC/float.c" | |||
| #include "flac/libFLAC/format.c" | |||
| #include "flac/libFLAC/lpc_flac.c" | |||
| #include "flac/libFLAC/md5.c" | |||
| #include "flac/libFLAC/memory.c" | |||
| #include "flac/libFLAC/stream_decoder.c" | |||
| #include "flac/libFLAC/stream_encoder.c" | |||
| #include "flac/libFLAC/stream_encoder_framing.c" | |||
| #include "flac/libFLAC/window_flac.c" | |||
| #undef VERSION | |||
| #else | |||
| #include <FLAC/all.h> | |||
| #endif | |||
| #if JUCE_CLANG | |||
| #pragma clang diagnostic pop | |||
| #endif | |||
| } | |||
| #undef max | |||
| #undef min | |||
| //============================================================================== | |||
| static const char* const flacFormatName = "FLAC file"; | |||
| //============================================================================== | |||
| class FlacReader : public AudioFormatReader | |||
| { | |||
| public: | |||
| FlacReader (InputStream* const in) | |||
| : AudioFormatReader (in, flacFormatName), | |||
| reservoirStart (0), | |||
| samplesInReservoir (0), | |||
| scanningForLength (false) | |||
| { | |||
| using namespace FlacNamespace; | |||
| lengthInSamples = 0; | |||
| decoder = FLAC__stream_decoder_new(); | |||
| ok = FLAC__stream_decoder_init_stream (decoder, | |||
| readCallback_, seekCallback_, tellCallback_, lengthCallback_, | |||
| eofCallback_, writeCallback_, metadataCallback_, errorCallback_, | |||
| this) == FLAC__STREAM_DECODER_INIT_STATUS_OK; | |||
| if (ok) | |||
| { | |||
| FLAC__stream_decoder_process_until_end_of_metadata (decoder); | |||
| if (lengthInSamples == 0 && sampleRate > 0) | |||
| { | |||
| // the length hasn't been stored in the metadata, so we'll need to | |||
| // work it out the length the hard way, by scanning the whole file.. | |||
| scanningForLength = true; | |||
| FLAC__stream_decoder_process_until_end_of_stream (decoder); | |||
| scanningForLength = false; | |||
| const int64 tempLength = lengthInSamples; | |||
| FLAC__stream_decoder_reset (decoder); | |||
| FLAC__stream_decoder_process_until_end_of_metadata (decoder); | |||
| lengthInSamples = tempLength; | |||
| } | |||
| } | |||
| } | |||
| ~FlacReader() | |||
| { | |||
| FlacNamespace::FLAC__stream_decoder_delete (decoder); | |||
| } | |||
| void useMetadata (const FlacNamespace::FLAC__StreamMetadata_StreamInfo& info) | |||
| { | |||
| sampleRate = info.sample_rate; | |||
| bitsPerSample = info.bits_per_sample; | |||
| lengthInSamples = (unsigned int) info.total_samples; | |||
| numChannels = info.channels; | |||
| reservoir.setSize ((int) numChannels, 2 * (int) info.max_blocksize, false, false, true); | |||
| } | |||
| // returns the number of samples read | |||
| bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | |||
| int64 startSampleInFile, int numSamples) override | |||
| { | |||
| using namespace FlacNamespace; | |||
| if (! ok) | |||
| return false; | |||
| while (numSamples > 0) | |||
| { | |||
| if (startSampleInFile >= reservoirStart | |||
| && startSampleInFile < reservoirStart + samplesInReservoir) | |||
| { | |||
| const int num = (int) jmin ((int64) numSamples, | |||
| reservoirStart + samplesInReservoir - startSampleInFile); | |||
| jassert (num > 0); | |||
| for (int i = jmin (numDestChannels, reservoir.getNumChannels()); --i >= 0;) | |||
| if (destSamples[i] != nullptr) | |||
| memcpy (destSamples[i] + startOffsetInDestBuffer, | |||
| reservoir.getReadPointer (i, (int) (startSampleInFile - reservoirStart)), | |||
| sizeof (int) * (size_t) num); | |||
| startOffsetInDestBuffer += num; | |||
| startSampleInFile += num; | |||
| numSamples -= num; | |||
| } | |||
| else | |||
| { | |||
| if (startSampleInFile >= (int) lengthInSamples) | |||
| { | |||
| samplesInReservoir = 0; | |||
| } | |||
| else if (startSampleInFile < reservoirStart | |||
| || startSampleInFile > reservoirStart + jmax (samplesInReservoir, 511)) | |||
| { | |||
| // had some problems with flac crashing if the read pos is aligned more | |||
| // accurately than this. Probably fixed in newer versions of the library, though. | |||
| reservoirStart = (int) (startSampleInFile & ~511); | |||
| samplesInReservoir = 0; | |||
| FLAC__stream_decoder_seek_absolute (decoder, (FLAC__uint64) reservoirStart); | |||
| } | |||
| else | |||
| { | |||
| reservoirStart += samplesInReservoir; | |||
| samplesInReservoir = 0; | |||
| FLAC__stream_decoder_process_single (decoder); | |||
| } | |||
| if (samplesInReservoir == 0) | |||
| break; | |||
| } | |||
| } | |||
| if (numSamples > 0) | |||
| { | |||
| for (int i = numDestChannels; --i >= 0;) | |||
| if (destSamples[i] != nullptr) | |||
| zeromem (destSamples[i] + startOffsetInDestBuffer, sizeof (int) * (size_t) numSamples); | |||
| } | |||
| return true; | |||
| } | |||
| void useSamples (const FlacNamespace::FLAC__int32* const buffer[], int numSamples) | |||
| { | |||
| if (scanningForLength) | |||
| { | |||
| lengthInSamples += numSamples; | |||
| } | |||
| else | |||
| { | |||
| if (numSamples > reservoir.getNumSamples()) | |||
| reservoir.setSize ((int) numChannels, numSamples, false, false, true); | |||
| const unsigned int bitsToShift = 32 - bitsPerSample; | |||
| for (int i = 0; i < (int) numChannels; ++i) | |||
| { | |||
| const FlacNamespace::FLAC__int32* src = buffer[i]; | |||
| int n = i; | |||
| while (src == 0 && n > 0) | |||
| src = buffer [--n]; | |||
| if (src != nullptr) | |||
| { | |||
| int* const dest = reinterpret_cast<int*> (reservoir.getWritePointer(i)); | |||
| for (int j = 0; j < numSamples; ++j) | |||
| dest[j] = src[j] << bitsToShift; | |||
| } | |||
| } | |||
| samplesInReservoir = numSamples; | |||
| } | |||
| } | |||
| //============================================================================== | |||
| static FlacNamespace::FLAC__StreamDecoderReadStatus readCallback_ (const FlacNamespace::FLAC__StreamDecoder*, FlacNamespace::FLAC__byte buffer[], size_t* bytes, void* client_data) | |||
| { | |||
| using namespace FlacNamespace; | |||
| *bytes = (size_t) static_cast<const FlacReader*> (client_data)->input->read (buffer, (int) *bytes); | |||
| return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; | |||
| } | |||
| static FlacNamespace::FLAC__StreamDecoderSeekStatus seekCallback_ (const FlacNamespace::FLAC__StreamDecoder*, FlacNamespace::FLAC__uint64 absolute_byte_offset, void* client_data) | |||
| { | |||
| using namespace FlacNamespace; | |||
| static_cast<const FlacReader*> (client_data)->input->setPosition ((int) absolute_byte_offset); | |||
| return FLAC__STREAM_DECODER_SEEK_STATUS_OK; | |||
| } | |||
| static FlacNamespace::FLAC__StreamDecoderTellStatus tellCallback_ (const FlacNamespace::FLAC__StreamDecoder*, FlacNamespace::FLAC__uint64* absolute_byte_offset, void* client_data) | |||
| { | |||
| using namespace FlacNamespace; | |||
| *absolute_byte_offset = (uint64) static_cast<const FlacReader*> (client_data)->input->getPosition(); | |||
| return FLAC__STREAM_DECODER_TELL_STATUS_OK; | |||
| } | |||
| static FlacNamespace::FLAC__StreamDecoderLengthStatus lengthCallback_ (const FlacNamespace::FLAC__StreamDecoder*, FlacNamespace::FLAC__uint64* stream_length, void* client_data) | |||
| { | |||
| using namespace FlacNamespace; | |||
| *stream_length = (uint64) static_cast<const FlacReader*> (client_data)->input->getTotalLength(); | |||
| return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; | |||
| } | |||
| static FlacNamespace::FLAC__bool eofCallback_ (const FlacNamespace::FLAC__StreamDecoder*, void* client_data) | |||
| { | |||
| return static_cast<const FlacReader*> (client_data)->input->isExhausted(); | |||
| } | |||
| static FlacNamespace::FLAC__StreamDecoderWriteStatus writeCallback_ (const FlacNamespace::FLAC__StreamDecoder*, | |||
| const FlacNamespace::FLAC__Frame* frame, | |||
| const FlacNamespace::FLAC__int32* const buffer[], | |||
| void* client_data) | |||
| { | |||
| using namespace FlacNamespace; | |||
| static_cast<FlacReader*> (client_data)->useSamples (buffer, (int) frame->header.blocksize); | |||
| return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; | |||
| } | |||
| static void metadataCallback_ (const FlacNamespace::FLAC__StreamDecoder*, | |||
| const FlacNamespace::FLAC__StreamMetadata* metadata, | |||
| void* client_data) | |||
| { | |||
| static_cast<FlacReader*> (client_data)->useMetadata (metadata->data.stream_info); | |||
| } | |||
| static void errorCallback_ (const FlacNamespace::FLAC__StreamDecoder*, FlacNamespace::FLAC__StreamDecoderErrorStatus, void*) | |||
| { | |||
| } | |||
| private: | |||
| FlacNamespace::FLAC__StreamDecoder* decoder; | |||
| AudioSampleBuffer reservoir; | |||
| int reservoirStart, samplesInReservoir; | |||
| bool ok, scanningForLength; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlacReader) | |||
| }; | |||
| //============================================================================== | |||
| class FlacWriter : public AudioFormatWriter | |||
| { | |||
| public: | |||
| FlacWriter (OutputStream* const out, double rate, uint32 numChans, uint32 bits, int qualityOptionIndex) | |||
| : AudioFormatWriter (out, flacFormatName, rate, numChans, bits) | |||
| { | |||
| using namespace FlacNamespace; | |||
| encoder = FLAC__stream_encoder_new(); | |||
| if (qualityOptionIndex > 0) | |||
| FLAC__stream_encoder_set_compression_level (encoder, (uint32) jmin (8, qualityOptionIndex)); | |||
| FLAC__stream_encoder_set_do_mid_side_stereo (encoder, numChannels == 2); | |||
| FLAC__stream_encoder_set_loose_mid_side_stereo (encoder, numChannels == 2); | |||
| FLAC__stream_encoder_set_channels (encoder, numChannels); | |||
| FLAC__stream_encoder_set_bits_per_sample (encoder, jmin ((unsigned int) 24, bitsPerSample)); | |||
| FLAC__stream_encoder_set_sample_rate (encoder, (unsigned int) sampleRate); | |||
| FLAC__stream_encoder_set_blocksize (encoder, 0); | |||
| FLAC__stream_encoder_set_do_escape_coding (encoder, true); | |||
| ok = FLAC__stream_encoder_init_stream (encoder, | |||
| encodeWriteCallback, encodeSeekCallback, | |||
| encodeTellCallback, encodeMetadataCallback, | |||
| this) == FLAC__STREAM_ENCODER_INIT_STATUS_OK; | |||
| } | |||
| ~FlacWriter() | |||
| { | |||
| if (ok) | |||
| { | |||
| FlacNamespace::FLAC__stream_encoder_finish (encoder); | |||
| output->flush(); | |||
| } | |||
| else | |||
| { | |||
| output = nullptr; // to stop the base class deleting this, as it needs to be returned | |||
| // to the caller of createWriter() | |||
| } | |||
| FlacNamespace::FLAC__stream_encoder_delete (encoder); | |||
| } | |||
| //============================================================================== | |||
| bool write (const int** samplesToWrite, int numSamples) override | |||
| { | |||
| using namespace FlacNamespace; | |||
| if (! ok) | |||
| return false; | |||
| HeapBlock<int*> channels; | |||
| HeapBlock<int> temp; | |||
| const int bitsToShift = 32 - (int) bitsPerSample; | |||
| if (bitsToShift > 0) | |||
| { | |||
| temp.malloc (numChannels * (size_t) numSamples); | |||
| channels.calloc (numChannels + 1); | |||
| for (unsigned int i = 0; i < numChannels; ++i) | |||
| { | |||
| if (samplesToWrite[i] == nullptr) | |||
| break; | |||
| int* const destData = temp.getData() + i * (size_t) numSamples; | |||
| channels[i] = destData; | |||
| for (int j = 0; j < numSamples; ++j) | |||
| destData[j] = (samplesToWrite[i][j] >> bitsToShift); | |||
| } | |||
| samplesToWrite = const_cast<const int**> (channels.getData()); | |||
| } | |||
| return FLAC__stream_encoder_process (encoder, (const FLAC__int32**) samplesToWrite, (unsigned) numSamples) != 0; | |||
| } | |||
| bool writeData (const void* const data, const int size) const | |||
| { | |||
| return output->write (data, (size_t) size); | |||
| } | |||
| static void packUint32 (FlacNamespace::FLAC__uint32 val, FlacNamespace::FLAC__byte* b, const int bytes) | |||
| { | |||
| b += bytes; | |||
| for (int i = 0; i < bytes; ++i) | |||
| { | |||
| *(--b) = (FlacNamespace::FLAC__byte) (val & 0xff); | |||
| val >>= 8; | |||
| } | |||
| } | |||
| void writeMetaData (const FlacNamespace::FLAC__StreamMetadata* metadata) | |||
| { | |||
| using namespace FlacNamespace; | |||
| const FLAC__StreamMetadata_StreamInfo& info = metadata->data.stream_info; | |||
| unsigned char buffer [FLAC__STREAM_METADATA_STREAMINFO_LENGTH]; | |||
| const unsigned int channelsMinus1 = info.channels - 1; | |||
| const unsigned int bitsMinus1 = info.bits_per_sample - 1; | |||
| packUint32 (info.min_blocksize, buffer, 2); | |||
| packUint32 (info.max_blocksize, buffer + 2, 2); | |||
| packUint32 (info.min_framesize, buffer + 4, 3); | |||
| packUint32 (info.max_framesize, buffer + 7, 3); | |||
| buffer[10] = (uint8) ((info.sample_rate >> 12) & 0xff); | |||
| buffer[11] = (uint8) ((info.sample_rate >> 4) & 0xff); | |||
| buffer[12] = (uint8) (((info.sample_rate & 0x0f) << 4) | (channelsMinus1 << 1) | (bitsMinus1 >> 4)); | |||
| buffer[13] = (FLAC__byte) (((bitsMinus1 & 0x0f) << 4) | (unsigned int) ((info.total_samples >> 32) & 0x0f)); | |||
| packUint32 ((FLAC__uint32) info.total_samples, buffer + 14, 4); | |||
| memcpy (buffer + 18, info.md5sum, 16); | |||
| const bool seekOk = output->setPosition (4); | |||
| (void) seekOk; | |||
| // if this fails, you've given it an output stream that can't seek! It needs | |||
| // to be able to seek back to write the header | |||
| jassert (seekOk); | |||
| output->writeIntBigEndian (FLAC__STREAM_METADATA_STREAMINFO_LENGTH); | |||
| output->write (buffer, FLAC__STREAM_METADATA_STREAMINFO_LENGTH); | |||
| } | |||
| //============================================================================== | |||
| static FlacNamespace::FLAC__StreamEncoderWriteStatus encodeWriteCallback (const FlacNamespace::FLAC__StreamEncoder*, | |||
| const FlacNamespace::FLAC__byte buffer[], | |||
| size_t bytes, | |||
| unsigned int /*samples*/, | |||
| unsigned int /*current_frame*/, | |||
| void* client_data) | |||
| { | |||
| using namespace FlacNamespace; | |||
| return static_cast<FlacWriter*> (client_data)->writeData (buffer, (int) bytes) | |||
| ? FLAC__STREAM_ENCODER_WRITE_STATUS_OK | |||
| : FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR; | |||
| } | |||
| static FlacNamespace::FLAC__StreamEncoderSeekStatus encodeSeekCallback (const FlacNamespace::FLAC__StreamEncoder*, FlacNamespace::FLAC__uint64, void*) | |||
| { | |||
| using namespace FlacNamespace; | |||
| return FLAC__STREAM_ENCODER_SEEK_STATUS_UNSUPPORTED; | |||
| } | |||
| static FlacNamespace::FLAC__StreamEncoderTellStatus encodeTellCallback (const FlacNamespace::FLAC__StreamEncoder*, FlacNamespace::FLAC__uint64* absolute_byte_offset, void* client_data) | |||
| { | |||
| using namespace FlacNamespace; | |||
| if (client_data == nullptr) | |||
| return FLAC__STREAM_ENCODER_TELL_STATUS_UNSUPPORTED; | |||
| *absolute_byte_offset = (FLAC__uint64) static_cast<FlacWriter*> (client_data)->output->getPosition(); | |||
| return FLAC__STREAM_ENCODER_TELL_STATUS_OK; | |||
| } | |||
| static void encodeMetadataCallback (const FlacNamespace::FLAC__StreamEncoder*, const FlacNamespace::FLAC__StreamMetadata* metadata, void* client_data) | |||
| { | |||
| static_cast<FlacWriter*> (client_data)->writeMetaData (metadata); | |||
| } | |||
| bool ok; | |||
| private: | |||
| FlacNamespace::FLAC__StreamEncoder* encoder; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlacWriter) | |||
| }; | |||
| //============================================================================== | |||
| FlacAudioFormat::FlacAudioFormat() | |||
| : AudioFormat (flacFormatName, ".flac") | |||
| { | |||
| } | |||
| FlacAudioFormat::~FlacAudioFormat() | |||
| { | |||
| } | |||
| Array<int> FlacAudioFormat::getPossibleSampleRates() | |||
| { | |||
| const int rates[] = { 8000, 11025, 12000, 16000, 22050, 32000, 44100, 48000, | |||
| 88200, 96000, 176400, 192000, 352800, 384000 }; | |||
| return Array<int> (rates, numElementsInArray (rates)); | |||
| } | |||
| Array<int> FlacAudioFormat::getPossibleBitDepths() | |||
| { | |||
| const int depths[] = { 16, 24 }; | |||
| return Array<int> (depths, numElementsInArray (depths)); | |||
| } | |||
| bool FlacAudioFormat::canDoStereo() { return true; } | |||
| bool FlacAudioFormat::canDoMono() { return true; } | |||
| bool FlacAudioFormat::isCompressed() { return true; } | |||
| AudioFormatReader* FlacAudioFormat::createReaderFor (InputStream* in, const bool deleteStreamIfOpeningFails) | |||
| { | |||
| ScopedPointer<FlacReader> r (new FlacReader (in)); | |||
| if (r->sampleRate > 0) | |||
| return r.release(); | |||
| if (! deleteStreamIfOpeningFails) | |||
| r->input = nullptr; | |||
| return nullptr; | |||
| } | |||
| AudioFormatWriter* FlacAudioFormat::createWriterFor (OutputStream* out, | |||
| double sampleRate, | |||
| unsigned int numberOfChannels, | |||
| int bitsPerSample, | |||
| const StringPairArray& /*metadataValues*/, | |||
| int qualityOptionIndex) | |||
| { | |||
| if (getPossibleBitDepths().contains (bitsPerSample)) | |||
| { | |||
| ScopedPointer<FlacWriter> w (new FlacWriter (out, sampleRate, numberOfChannels, | |||
| (uint32) bitsPerSample, qualityOptionIndex)); | |||
| if (w->ok) | |||
| return w.release(); | |||
| } | |||
| return nullptr; | |||
| } | |||
| StringArray FlacAudioFormat::getQualityOptions() | |||
| { | |||
| static const char* options[] = { "0 (Fastest)", "1", "2", "3", "4", "5 (Default)","6", "7", "8 (Highest quality)", 0 }; | |||
| return StringArray (options); | |||
| } | |||
| #endif | |||
| @@ -1,65 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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_FLAC || defined (DOXYGEN) | |||
| //============================================================================== | |||
| /** | |||
| Reads and writes the lossless-compression FLAC audio format. | |||
| To compile this, you'll need to set the JUCE_USE_FLAC flag. | |||
| @see AudioFormat | |||
| */ | |||
| class JUCE_API FlacAudioFormat : public AudioFormat | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| FlacAudioFormat(); | |||
| ~FlacAudioFormat(); | |||
| //============================================================================== | |||
| Array<int> getPossibleSampleRates() override; | |||
| Array<int> getPossibleBitDepths() override; | |||
| bool canDoStereo() override; | |||
| bool canDoMono() override; | |||
| bool isCompressed() override; | |||
| StringArray getQualityOptions() override; | |||
| //============================================================================== | |||
| AudioFormatReader* createReaderFor (InputStream* sourceStream, | |||
| bool deleteStreamIfOpeningFails) override; | |||
| AudioFormatWriter* createWriterFor (OutputStream* streamToWriteTo, | |||
| double sampleRateToUse, | |||
| unsigned int numberOfChannels, | |||
| int bitsPerSample, | |||
| const StringPairArray& metadataValues, | |||
| int qualityOptionIndex) override; | |||
| private: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlacAudioFormat) | |||
| }; | |||
| #endif | |||
| @@ -1,222 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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_LAME_AUDIO_FORMAT | |||
| class LAMEEncoderAudioFormat::Writer : public AudioFormatWriter | |||
| { | |||
| public: | |||
| Writer (OutputStream* destStream, const String& formatName, | |||
| const File& appFile, int vbr, int cbr, | |||
| double sampleRate, unsigned int numberOfChannels, | |||
| unsigned int bitsPerSample, const StringPairArray& metadata) | |||
| : AudioFormatWriter (destStream, formatName, sampleRate, | |||
| numberOfChannels, bitsPerSample), | |||
| vbrLevel (vbr), cbrBitrate (cbr), | |||
| tempWav (".wav") | |||
| { | |||
| WavAudioFormat wavFormat; | |||
| if (FileOutputStream* out = tempWav.getFile().createOutputStream()) | |||
| { | |||
| writer = wavFormat.createWriterFor (out, sampleRate, numChannels, | |||
| bitsPerSample, metadata, 0); | |||
| args.add (appFile.getFullPathName()); | |||
| args.add ("--quiet"); | |||
| if (cbrBitrate == 0) | |||
| { | |||
| args.add ("--vbr-new"); | |||
| args.add ("-V"); | |||
| args.add (String (vbrLevel)); | |||
| } | |||
| else | |||
| { | |||
| args.add ("--cbr"); | |||
| args.add ("-b"); | |||
| args.add (String (cbrBitrate)); | |||
| } | |||
| addMetadataArg (metadata, "id3title", "--tt"); | |||
| addMetadataArg (metadata, "id3artist", "--ta"); | |||
| addMetadataArg (metadata, "id3album", "--tl"); | |||
| addMetadataArg (metadata, "id3comment", "--tc"); | |||
| addMetadataArg (metadata, "id3date", "--ty"); | |||
| addMetadataArg (metadata, "id3genre", "--tg"); | |||
| addMetadataArg (metadata, "id3trackNumber", "--tn"); | |||
| } | |||
| } | |||
| void addMetadataArg (const StringPairArray& metadata, const char* key, const char* lameFlag) | |||
| { | |||
| const String value (metadata.getValue (key, String())); | |||
| if (value.isNotEmpty()) | |||
| { | |||
| args.add (lameFlag); | |||
| args.add (value); | |||
| } | |||
| } | |||
| ~Writer() | |||
| { | |||
| if (writer != nullptr) | |||
| { | |||
| writer = nullptr; | |||
| if (! convertToMP3()) | |||
| convertToMP3(); // try again | |||
| } | |||
| } | |||
| bool write (const int** samplesToWrite, int numSamples) | |||
| { | |||
| return writer != nullptr && writer->write (samplesToWrite, numSamples); | |||
| } | |||
| private: | |||
| int vbrLevel, cbrBitrate; | |||
| TemporaryFile tempWav; | |||
| ScopedPointer<AudioFormatWriter> writer; | |||
| StringArray args; | |||
| bool runLameChildProcess (const TemporaryFile& tempMP3, const StringArray& processArgs) const | |||
| { | |||
| ChildProcess cp; | |||
| if (cp.start (processArgs)) | |||
| { | |||
| const String childOutput (cp.readAllProcessOutput()); | |||
| DBG (childOutput); (void) childOutput; | |||
| cp.waitForProcessToFinish (10000); | |||
| return tempMP3.getFile().getSize() > 0; | |||
| } | |||
| return false; | |||
| } | |||
| bool convertToMP3() const | |||
| { | |||
| TemporaryFile tempMP3 (".mp3"); | |||
| StringArray args2 (args); | |||
| args2.add (tempWav.getFile().getFullPathName()); | |||
| args2.add (tempMP3.getFile().getFullPathName()); | |||
| DBG (args2.joinIntoString (" ")); | |||
| if (runLameChildProcess (tempMP3, args2)) | |||
| { | |||
| FileInputStream fis (tempMP3.getFile()); | |||
| if (fis.openedOk() && output->writeFromInputStream (fis, -1) > 0) | |||
| { | |||
| output->flush(); | |||
| return true; | |||
| } | |||
| } | |||
| return false; | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Writer) | |||
| }; | |||
| //============================================================================== | |||
| LAMEEncoderAudioFormat::LAMEEncoderAudioFormat (const File& lameApplication) | |||
| : AudioFormat ("MP3 file", ".mp3"), | |||
| lameApp (lameApplication) | |||
| { | |||
| } | |||
| LAMEEncoderAudioFormat::~LAMEEncoderAudioFormat() | |||
| { | |||
| } | |||
| bool LAMEEncoderAudioFormat::canHandleFile (const File&) | |||
| { | |||
| return false; | |||
| } | |||
| Array<int> LAMEEncoderAudioFormat::getPossibleSampleRates() | |||
| { | |||
| const int rates[] = { 32000, 44100, 48000, 0 }; | |||
| return Array<int> (rates); | |||
| } | |||
| Array<int> LAMEEncoderAudioFormat::getPossibleBitDepths() | |||
| { | |||
| const int depths[] = { 16, 0 }; | |||
| return Array<int> (depths); | |||
| } | |||
| bool LAMEEncoderAudioFormat::canDoStereo() { return true; } | |||
| bool LAMEEncoderAudioFormat::canDoMono() { return true; } | |||
| bool LAMEEncoderAudioFormat::isCompressed() { return true; } | |||
| StringArray LAMEEncoderAudioFormat::getQualityOptions() | |||
| { | |||
| static const char* vbrOptions[] = { "VBR quality 0 (best)", "VBR quality 1", "VBR quality 2", "VBR quality 3", | |||
| "VBR quality 4 (normal)", "VBR quality 5", "VBR quality 6", "VBR quality 7", | |||
| "VBR quality 8", "VBR quality 9 (smallest)", nullptr }; | |||
| StringArray opts (vbrOptions); | |||
| const int cbrRates[] = { 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 }; | |||
| for (int i = 0; i < numElementsInArray (cbrRates); ++i) | |||
| opts.add (String (cbrRates[i]) + " Kb/s CBR"); | |||
| return opts; | |||
| } | |||
| AudioFormatReader* LAMEEncoderAudioFormat::createReaderFor (InputStream*, const bool) | |||
| { | |||
| return nullptr; | |||
| } | |||
| AudioFormatWriter* LAMEEncoderAudioFormat::createWriterFor (OutputStream* streamToWriteTo, | |||
| double sampleRateToUse, | |||
| unsigned int numberOfChannels, | |||
| int bitsPerSample, | |||
| const StringPairArray& metadataValues, | |||
| int qualityOptionIndex) | |||
| { | |||
| int vbr = 4; | |||
| int cbr = 0; | |||
| const String qual (getQualityOptions() [qualityOptionIndex]); | |||
| if (qual.contains ("VBR")) | |||
| vbr = qual.retainCharacters ("0123456789").getIntValue(); | |||
| else | |||
| cbr = qual.getIntValue(); | |||
| return new Writer (streamToWriteTo, getFormatName(), lameApp, vbr, cbr, | |||
| sampleRateToUse, numberOfChannels, bitsPerSample, metadataValues); | |||
| } | |||
| #endif | |||
| @@ -1,71 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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_LAME_AUDIO_FORMAT || defined (DOXYGEN) | |||
| //============================================================================== | |||
| /** | |||
| An AudioFormat class which can use an installed version of the LAME mp3 | |||
| encoder to encode a file. | |||
| This format can't read MP3s, it just writes them. Internally, the | |||
| AudioFormatWriter object that is returned writes the incoming audio data | |||
| to a temporary WAV file, and then when the writer is deleted, it invokes | |||
| the LAME executable to convert the data to an MP3, whose data is then | |||
| piped into the original OutputStream that was used when first creating | |||
| the writer. | |||
| @see AudioFormat | |||
| */ | |||
| class JUCE_API LAMEEncoderAudioFormat : public AudioFormat | |||
| { | |||
| public: | |||
| /** Creates a LAMEEncoderAudioFormat that expects to find a working LAME | |||
| executable at the location given. | |||
| */ | |||
| LAMEEncoderAudioFormat (const File& lameExecutableToUse); | |||
| ~LAMEEncoderAudioFormat(); | |||
| bool canHandleFile (const File&); | |||
| Array<int> getPossibleSampleRates(); | |||
| Array<int> getPossibleBitDepths(); | |||
| bool canDoStereo(); | |||
| bool canDoMono(); | |||
| bool isCompressed(); | |||
| StringArray getQualityOptions(); | |||
| AudioFormatReader* createReaderFor (InputStream*, bool deleteStreamIfOpeningFails); | |||
| AudioFormatWriter* createWriterFor (OutputStream*, double sampleRateToUse, | |||
| unsigned int numberOfChannels, int bitsPerSample, | |||
| const StringPairArray& metadataValues, int qualityOptionIndex); | |||
| private: | |||
| File lameApp; | |||
| class Writer; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LAMEEncoderAudioFormat) | |||
| }; | |||
| #endif | |||