mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-13 00:04:19 +00:00
Added Animated App template and examples
This commit is contained in:
parent
fefcf7aca6
commit
ff6520a89a
1141 changed files with 438491 additions and 94 deletions
|
|
@ -0,0 +1,600 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
void AudioDataConverters::convertFloatToInt16LE (const float* source, void* dest, int numSamples, const int destBytesPerSample)
|
||||
{
|
||||
const double maxVal = (double) 0x7fff;
|
||||
char* intData = static_cast<char*> (dest);
|
||||
|
||||
if (dest != (void*) source || destBytesPerSample <= 4)
|
||||
{
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
{
|
||||
*(uint16*) intData = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
|
||||
intData += destBytesPerSample;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
intData += destBytesPerSample * numSamples;
|
||||
|
||||
for (int i = numSamples; --i >= 0;)
|
||||
{
|
||||
intData -= destBytesPerSample;
|
||||
*(uint16*) intData = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioDataConverters::convertFloatToInt16BE (const float* source, void* dest, int numSamples, const int destBytesPerSample)
|
||||
{
|
||||
const double maxVal = (double) 0x7fff;
|
||||
char* intData = static_cast<char*> (dest);
|
||||
|
||||
if (dest != (void*) source || destBytesPerSample <= 4)
|
||||
{
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
{
|
||||
*(uint16*) intData = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
|
||||
intData += destBytesPerSample;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
intData += destBytesPerSample * numSamples;
|
||||
|
||||
for (int i = numSamples; --i >= 0;)
|
||||
{
|
||||
intData -= destBytesPerSample;
|
||||
*(uint16*) intData = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioDataConverters::convertFloatToInt24LE (const float* source, void* dest, int numSamples, const int destBytesPerSample)
|
||||
{
|
||||
const double maxVal = (double) 0x7fffff;
|
||||
char* intData = static_cast<char*> (dest);
|
||||
|
||||
if (dest != (void*) source || destBytesPerSample <= 4)
|
||||
{
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
{
|
||||
ByteOrder::littleEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData);
|
||||
intData += destBytesPerSample;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
intData += destBytesPerSample * numSamples;
|
||||
|
||||
for (int i = numSamples; --i >= 0;)
|
||||
{
|
||||
intData -= destBytesPerSample;
|
||||
ByteOrder::littleEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioDataConverters::convertFloatToInt24BE (const float* source, void* dest, int numSamples, const int destBytesPerSample)
|
||||
{
|
||||
const double maxVal = (double) 0x7fffff;
|
||||
char* intData = static_cast<char*> (dest);
|
||||
|
||||
if (dest != (void*) source || destBytesPerSample <= 4)
|
||||
{
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
{
|
||||
ByteOrder::bigEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData);
|
||||
intData += destBytesPerSample;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
intData += destBytesPerSample * numSamples;
|
||||
|
||||
for (int i = numSamples; --i >= 0;)
|
||||
{
|
||||
intData -= destBytesPerSample;
|
||||
ByteOrder::bigEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioDataConverters::convertFloatToInt32LE (const float* source, void* dest, int numSamples, const int destBytesPerSample)
|
||||
{
|
||||
const double maxVal = (double) 0x7fffffff;
|
||||
char* intData = static_cast<char*> (dest);
|
||||
|
||||
if (dest != (void*) source || destBytesPerSample <= 4)
|
||||
{
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
{
|
||||
*(uint32*)intData = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
|
||||
intData += destBytesPerSample;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
intData += destBytesPerSample * numSamples;
|
||||
|
||||
for (int i = numSamples; --i >= 0;)
|
||||
{
|
||||
intData -= destBytesPerSample;
|
||||
*(uint32*)intData = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioDataConverters::convertFloatToInt32BE (const float* source, void* dest, int numSamples, const int destBytesPerSample)
|
||||
{
|
||||
const double maxVal = (double) 0x7fffffff;
|
||||
char* intData = static_cast<char*> (dest);
|
||||
|
||||
if (dest != (void*) source || destBytesPerSample <= 4)
|
||||
{
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
{
|
||||
*(uint32*)intData = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
|
||||
intData += destBytesPerSample;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
intData += destBytesPerSample * numSamples;
|
||||
|
||||
for (int i = numSamples; --i >= 0;)
|
||||
{
|
||||
intData -= destBytesPerSample;
|
||||
*(uint32*)intData = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioDataConverters::convertFloatToFloat32LE (const float* source, void* dest, int numSamples, const int destBytesPerSample)
|
||||
{
|
||||
jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data!
|
||||
|
||||
char* d = static_cast<char*> (dest);
|
||||
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
{
|
||||
*(float*) d = source[i];
|
||||
|
||||
#if JUCE_BIG_ENDIAN
|
||||
*(uint32*) d = ByteOrder::swap (*(uint32*) d);
|
||||
#endif
|
||||
|
||||
d += destBytesPerSample;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioDataConverters::convertFloatToFloat32BE (const float* source, void* dest, int numSamples, const int destBytesPerSample)
|
||||
{
|
||||
jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data!
|
||||
|
||||
char* d = static_cast<char*> (dest);
|
||||
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
{
|
||||
*(float*) d = source[i];
|
||||
|
||||
#if JUCE_LITTLE_ENDIAN
|
||||
*(uint32*) d = ByteOrder::swap (*(uint32*) d);
|
||||
#endif
|
||||
|
||||
d += destBytesPerSample;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void AudioDataConverters::convertInt16LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample)
|
||||
{
|
||||
const float scale = 1.0f / 0x7fff;
|
||||
const char* intData = static_cast<const char*> (source);
|
||||
|
||||
if (source != (void*) dest || srcBytesPerSample >= 4)
|
||||
{
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
{
|
||||
dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*(uint16*)intData);
|
||||
intData += srcBytesPerSample;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
intData += srcBytesPerSample * numSamples;
|
||||
|
||||
for (int i = numSamples; --i >= 0;)
|
||||
{
|
||||
intData -= srcBytesPerSample;
|
||||
dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*(uint16*)intData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioDataConverters::convertInt16BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample)
|
||||
{
|
||||
const float scale = 1.0f / 0x7fff;
|
||||
const char* intData = static_cast<const char*> (source);
|
||||
|
||||
if (source != (void*) dest || srcBytesPerSample >= 4)
|
||||
{
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
{
|
||||
dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*(uint16*)intData);
|
||||
intData += srcBytesPerSample;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
intData += srcBytesPerSample * numSamples;
|
||||
|
||||
for (int i = numSamples; --i >= 0;)
|
||||
{
|
||||
intData -= srcBytesPerSample;
|
||||
dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*(uint16*)intData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioDataConverters::convertInt24LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample)
|
||||
{
|
||||
const float scale = 1.0f / 0x7fffff;
|
||||
const char* intData = static_cast<const char*> (source);
|
||||
|
||||
if (source != (void*) dest || srcBytesPerSample >= 4)
|
||||
{
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
{
|
||||
dest[i] = scale * (short) ByteOrder::littleEndian24Bit (intData);
|
||||
intData += srcBytesPerSample;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
intData += srcBytesPerSample * numSamples;
|
||||
|
||||
for (int i = numSamples; --i >= 0;)
|
||||
{
|
||||
intData -= srcBytesPerSample;
|
||||
dest[i] = scale * (short) ByteOrder::littleEndian24Bit (intData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioDataConverters::convertInt24BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample)
|
||||
{
|
||||
const float scale = 1.0f / 0x7fffff;
|
||||
const char* intData = static_cast<const char*> (source);
|
||||
|
||||
if (source != (void*) dest || srcBytesPerSample >= 4)
|
||||
{
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
{
|
||||
dest[i] = scale * (short) ByteOrder::bigEndian24Bit (intData);
|
||||
intData += srcBytesPerSample;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
intData += srcBytesPerSample * numSamples;
|
||||
|
||||
for (int i = numSamples; --i >= 0;)
|
||||
{
|
||||
intData -= srcBytesPerSample;
|
||||
dest[i] = scale * (short) ByteOrder::bigEndian24Bit (intData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioDataConverters::convertInt32LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample)
|
||||
{
|
||||
const float scale = 1.0f / 0x7fffffff;
|
||||
const char* intData = static_cast<const char*> (source);
|
||||
|
||||
if (source != (void*) dest || srcBytesPerSample >= 4)
|
||||
{
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
{
|
||||
dest[i] = scale * (int) ByteOrder::swapIfBigEndian (*(uint32*) intData);
|
||||
intData += srcBytesPerSample;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
intData += srcBytesPerSample * numSamples;
|
||||
|
||||
for (int i = numSamples; --i >= 0;)
|
||||
{
|
||||
intData -= srcBytesPerSample;
|
||||
dest[i] = scale * (int) ByteOrder::swapIfBigEndian (*(uint32*) intData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioDataConverters::convertInt32BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample)
|
||||
{
|
||||
const float scale = 1.0f / 0x7fffffff;
|
||||
const char* intData = static_cast<const char*> (source);
|
||||
|
||||
if (source != (void*) dest || srcBytesPerSample >= 4)
|
||||
{
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
{
|
||||
dest[i] = scale * (int) ByteOrder::swapIfLittleEndian (*(uint32*) intData);
|
||||
intData += srcBytesPerSample;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
intData += srcBytesPerSample * numSamples;
|
||||
|
||||
for (int i = numSamples; --i >= 0;)
|
||||
{
|
||||
intData -= srcBytesPerSample;
|
||||
dest[i] = scale * (int) ByteOrder::swapIfLittleEndian (*(uint32*) intData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioDataConverters::convertFloat32LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample)
|
||||
{
|
||||
const char* s = static_cast<const char*> (source);
|
||||
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
{
|
||||
dest[i] = *(float*)s;
|
||||
|
||||
#if JUCE_BIG_ENDIAN
|
||||
uint32* const d = (uint32*) (dest + i);
|
||||
*d = ByteOrder::swap (*d);
|
||||
#endif
|
||||
|
||||
s += srcBytesPerSample;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioDataConverters::convertFloat32BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample)
|
||||
{
|
||||
const char* s = static_cast<const char*> (source);
|
||||
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
{
|
||||
dest[i] = *(float*)s;
|
||||
|
||||
#if JUCE_LITTLE_ENDIAN
|
||||
uint32* const d = (uint32*) (dest + i);
|
||||
*d = ByteOrder::swap (*d);
|
||||
#endif
|
||||
|
||||
s += srcBytesPerSample;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
void AudioDataConverters::convertFloatToFormat (const DataFormat destFormat,
|
||||
const float* const source,
|
||||
void* const dest,
|
||||
const int numSamples)
|
||||
{
|
||||
switch (destFormat)
|
||||
{
|
||||
case int16LE: convertFloatToInt16LE (source, dest, numSamples); break;
|
||||
case int16BE: convertFloatToInt16BE (source, dest, numSamples); break;
|
||||
case int24LE: convertFloatToInt24LE (source, dest, numSamples); break;
|
||||
case int24BE: convertFloatToInt24BE (source, dest, numSamples); break;
|
||||
case int32LE: convertFloatToInt32LE (source, dest, numSamples); break;
|
||||
case int32BE: convertFloatToInt32BE (source, dest, numSamples); break;
|
||||
case float32LE: convertFloatToFloat32LE (source, dest, numSamples); break;
|
||||
case float32BE: convertFloatToFloat32BE (source, dest, numSamples); break;
|
||||
default: jassertfalse; break;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioDataConverters::convertFormatToFloat (const DataFormat sourceFormat,
|
||||
const void* const source,
|
||||
float* const dest,
|
||||
const int numSamples)
|
||||
{
|
||||
switch (sourceFormat)
|
||||
{
|
||||
case int16LE: convertInt16LEToFloat (source, dest, numSamples); break;
|
||||
case int16BE: convertInt16BEToFloat (source, dest, numSamples); break;
|
||||
case int24LE: convertInt24LEToFloat (source, dest, numSamples); break;
|
||||
case int24BE: convertInt24BEToFloat (source, dest, numSamples); break;
|
||||
case int32LE: convertInt32LEToFloat (source, dest, numSamples); break;
|
||||
case int32BE: convertInt32BEToFloat (source, dest, numSamples); break;
|
||||
case float32LE: convertFloat32LEToFloat (source, dest, numSamples); break;
|
||||
case float32BE: convertFloat32BEToFloat (source, dest, numSamples); break;
|
||||
default: jassertfalse; break;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void AudioDataConverters::interleaveSamples (const float** const source,
|
||||
float* const dest,
|
||||
const int numSamples,
|
||||
const int numChannels)
|
||||
{
|
||||
for (int chan = 0; chan < numChannels; ++chan)
|
||||
{
|
||||
int i = chan;
|
||||
const float* src = source [chan];
|
||||
|
||||
for (int j = 0; j < numSamples; ++j)
|
||||
{
|
||||
dest [i] = src [j];
|
||||
i += numChannels;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioDataConverters::deinterleaveSamples (const float* const source,
|
||||
float** const dest,
|
||||
const int numSamples,
|
||||
const int numChannels)
|
||||
{
|
||||
for (int chan = 0; chan < numChannels; ++chan)
|
||||
{
|
||||
int i = chan;
|
||||
float* dst = dest [chan];
|
||||
|
||||
for (int j = 0; j < numSamples; ++j)
|
||||
{
|
||||
dst [j] = source [i];
|
||||
i += numChannels;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
class AudioConversionTests : public UnitTest
|
||||
{
|
||||
public:
|
||||
AudioConversionTests() : UnitTest ("Audio data conversion") {}
|
||||
|
||||
template <class F1, class E1, class F2, class E2>
|
||||
struct Test5
|
||||
{
|
||||
static void test (UnitTest& unitTest, 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
|
||||
|
|
@ -0,0 +1,710 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,670 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
@ -0,0 +1,526 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,893 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,244 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace LagrangeHelpers
|
||||
{
|
||||
template <int k>
|
||||
struct ResampleHelper
|
||||
{
|
||||
static forcedinline void calc (float& a, float b) { a *= b * (1.0f / k); }
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ResampleHelper <0>
|
||||
{
|
||||
static forcedinline void calc (float&, float) {}
|
||||
};
|
||||
|
||||
template <int k>
|
||||
static forcedinline float calcCoefficient (float input, const float offset) noexcept
|
||||
{
|
||||
ResampleHelper <0 - k>::calc (input, -2.0f - offset);
|
||||
ResampleHelper <1 - k>::calc (input, -1.0f - offset);
|
||||
ResampleHelper <2 - k>::calc (input, 0.0f - offset);
|
||||
ResampleHelper <3 - k>::calc (input, 1.0f - offset);
|
||||
ResampleHelper <4 - k>::calc (input, 2.0f - offset);
|
||||
return input;
|
||||
}
|
||||
|
||||
static forcedinline float valueAtOffset (const float* const inputs, const float offset) noexcept
|
||||
{
|
||||
return calcCoefficient<0> (inputs[4], offset)
|
||||
+ calcCoefficient<1> (inputs[3], offset)
|
||||
+ calcCoefficient<2> (inputs[2], offset)
|
||||
+ calcCoefficient<3> (inputs[1], offset)
|
||||
+ calcCoefficient<4> (inputs[0], offset);
|
||||
}
|
||||
|
||||
static forcedinline void push (float* inputs, const float newValue) noexcept
|
||||
{
|
||||
inputs[4] = inputs[3];
|
||||
inputs[3] = inputs[2];
|
||||
inputs[2] = inputs[1];
|
||||
inputs[1] = inputs[0];
|
||||
inputs[0] = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
LagrangeInterpolator::LagrangeInterpolator() { reset(); }
|
||||
LagrangeInterpolator::~LagrangeInterpolator() {}
|
||||
|
||||
void LagrangeInterpolator::reset() noexcept
|
||||
{
|
||||
subSamplePos = 1.0;
|
||||
|
||||
for (int i = 0; i < numElementsInArray (lastInputSamples); ++i)
|
||||
lastInputSamples[i] = 0;
|
||||
}
|
||||
|
||||
int LagrangeInterpolator::process (const double actualRatio, const float* in,
|
||||
float* out, const int numOut) noexcept
|
||||
{
|
||||
if (actualRatio == 1.0)
|
||||
{
|
||||
memcpy (out, in, (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);
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_LAGRANGEINTERPOLATOR_H_INCLUDED
|
||||
#define JUCE_LAGRANGEINTERPOLATOR_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Interpolator for resampling a stream of floats using 4-point lagrange interpolation.
|
||||
|
||||
Note that the resampler is stateful, so when there's a break in the continuity
|
||||
of the input stream you're feeding it, you should call reset() before feeding
|
||||
it any new data. And like with any other stateful filter, if you're resampling
|
||||
multiple channels, make sure each one uses its own LagrangeInterpolator
|
||||
object.
|
||||
*/
|
||||
class JUCE_API LagrangeInterpolator
|
||||
{
|
||||
public:
|
||||
LagrangeInterpolator();
|
||||
~LagrangeInterpolator();
|
||||
|
||||
/** Resets the state of the interpolator.
|
||||
Call this when there's a break in the continuity of the input data stream.
|
||||
*/
|
||||
void reset() noexcept;
|
||||
|
||||
/** Resamples a stream of samples.
|
||||
|
||||
@param speedRatio the number of input samples to use for each output sample
|
||||
@param inputSamples the source data to read from. This must contain at
|
||||
least (speedRatio * numOutputSamplesToProduce) samples.
|
||||
@param outputSamples the buffer to write the results into
|
||||
@param numOutputSamplesToProduce the number of output samples that should be created
|
||||
|
||||
@returns the actual number of input samples that were used
|
||||
*/
|
||||
int process (double speedRatio,
|
||||
const float* inputSamples,
|
||||
float* outputSamples,
|
||||
int numOutputSamplesToProduce) noexcept;
|
||||
|
||||
/** Resamples a stream of samples, adding the results to the output data
|
||||
with a gain.
|
||||
|
||||
@param speedRatio the number of input samples to use for each output sample
|
||||
@param inputSamples the source data to read from. This must contain at
|
||||
least (speedRatio * numOutputSamplesToProduce) samples.
|
||||
@param outputSamples the buffer to write the results to - the result values will be added
|
||||
to any pre-existing data in this buffer after being multiplied by
|
||||
the gain factor
|
||||
@param numOutputSamplesToProduce the number of output samples that should be created
|
||||
@param gain a gain factor to multiply the resulting samples by before
|
||||
adding them to the destination buffer
|
||||
|
||||
@returns the actual number of input samples that were used
|
||||
*/
|
||||
int processAdding (double speedRatio,
|
||||
const float* inputSamples,
|
||||
float* outputSamples,
|
||||
int numOutputSamplesToProduce,
|
||||
float gain) noexcept;
|
||||
|
||||
private:
|
||||
float lastInputSamples[5];
|
||||
double subSamplePos;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LagrangeInterpolator)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_LAGRANGEINTERPOLATOR_H_INCLUDED
|
||||
|
|
@ -0,0 +1,323 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_REVERB_H_INCLUDED
|
||||
#define JUCE_REVERB_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Performs a simple reverb effect on a stream of audio data.
|
||||
|
||||
This is a simple stereo reverb, based on the technique and tunings used in FreeVerb.
|
||||
Use setSampleRate() to prepare it, and then call processStereo() or processMono() to
|
||||
apply the reverb to your audio data.
|
||||
|
||||
@see ReverbAudioSource
|
||||
*/
|
||||
class Reverb
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
Reverb()
|
||||
{
|
||||
setParameters (Parameters());
|
||||
setSampleRate (44100.0);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Holds the parameters being used by a Reverb object. */
|
||||
struct Parameters
|
||||
{
|
||||
Parameters() noexcept
|
||||
: roomSize (0.5f),
|
||||
damping (0.5f),
|
||||
wetLevel (0.33f),
|
||||
dryLevel (0.4f),
|
||||
width (1.0f),
|
||||
freezeMode (0)
|
||||
{}
|
||||
|
||||
float roomSize; /**< Room size, 0 to 1.0, where 1.0 is big, 0 is small. */
|
||||
float damping; /**< Damping, 0 to 1.0, where 0 is not damped, 1.0 is fully damped. */
|
||||
float wetLevel; /**< Wet level, 0 to 1.0 */
|
||||
float dryLevel; /**< Dry level, 0 to 1.0 */
|
||||
float width; /**< Reverb width, 0 to 1.0, where 1.0 is very wide. */
|
||||
float freezeMode; /**< Freeze mode - values < 0.5 are "normal" mode, values > 0.5
|
||||
put the reverb into a continuous feedback loop. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the reverb's current parameters. */
|
||||
const Parameters& getParameters() const noexcept { return parameters; }
|
||||
|
||||
/** Applies a new set of parameters to the reverb.
|
||||
Note that this doesn't attempt to lock the reverb, so if you call this in parallel with
|
||||
the process method, you may get artifacts.
|
||||
*/
|
||||
void setParameters (const Parameters& newParams)
|
||||
{
|
||||
const float wetScaleFactor = 3.0f;
|
||||
const float dryScaleFactor = 2.0f;
|
||||
|
||||
const float wet = newParams.wetLevel * wetScaleFactor;
|
||||
wet1 = wet * (newParams.width * 0.5f + 0.5f);
|
||||
wet2 = wet * (1.0f - newParams.width) * 0.5f;
|
||||
dry = newParams.dryLevel * dryScaleFactor;
|
||||
gain = isFrozen (newParams.freezeMode) ? 0.0f : 0.015f;
|
||||
parameters = newParams;
|
||||
shouldUpdateDamping = true;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the sample rate that will be used for the reverb.
|
||||
You must call this before the process methods, in order to tell it the correct sample rate.
|
||||
*/
|
||||
void setSampleRate (const double sampleRate)
|
||||
{
|
||||
jassert (sampleRate > 0);
|
||||
|
||||
static const short combTunings[] = { 1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617 }; // (at 44100Hz)
|
||||
static const short allPassTunings[] = { 556, 441, 341, 225 };
|
||||
const int stereoSpread = 23;
|
||||
const int intSampleRate = (int) sampleRate;
|
||||
|
||||
for (int i = 0; i < numCombs; ++i)
|
||||
{
|
||||
comb[0][i].setSize ((intSampleRate * combTunings[i]) / 44100);
|
||||
comb[1][i].setSize ((intSampleRate * (combTunings[i] + stereoSpread)) / 44100);
|
||||
}
|
||||
|
||||
for (int i = 0; i < numAllPasses; ++i)
|
||||
{
|
||||
allPass[0][i].setSize ((intSampleRate * allPassTunings[i]) / 44100);
|
||||
allPass[1][i].setSize ((intSampleRate * (allPassTunings[i] + stereoSpread)) / 44100);
|
||||
}
|
||||
|
||||
shouldUpdateDamping = true;
|
||||
}
|
||||
|
||||
/** Clears the reverb's buffers. */
|
||||
void reset()
|
||||
{
|
||||
for (int j = 0; j < numChannels; ++j)
|
||||
{
|
||||
for (int i = 0; i < numCombs; ++i)
|
||||
comb[j][i].clear();
|
||||
|
||||
for (int i = 0; i < numAllPasses; ++i)
|
||||
allPass[j][i].clear();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Applies the reverb to two stereo channels of audio data. */
|
||||
void processStereo (float* const left, float* const right, const int numSamples) noexcept
|
||||
{
|
||||
jassert (left != nullptr && right != nullptr);
|
||||
|
||||
if (shouldUpdateDamping)
|
||||
updateDamping();
|
||||
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
{
|
||||
const float input = (left[i] + right[i]) * gain;
|
||||
float outL = 0, outR = 0;
|
||||
|
||||
for (int j = 0; j < numCombs; ++j) // accumulate the comb filters in parallel
|
||||
{
|
||||
outL += comb[0][j].process (input);
|
||||
outR += comb[1][j].process (input);
|
||||
}
|
||||
|
||||
for (int j = 0; j < numAllPasses; ++j) // run the allpass filters in series
|
||||
{
|
||||
outL = allPass[0][j].process (outL);
|
||||
outR = allPass[1][j].process (outR);
|
||||
}
|
||||
|
||||
left[i] = outL * wet1 + outR * wet2 + left[i] * dry;
|
||||
right[i] = outR * wet1 + outL * wet2 + right[i] * dry;
|
||||
}
|
||||
}
|
||||
|
||||
/** Applies the reverb to a single mono channel of audio data. */
|
||||
void processMono (float* const samples, const int numSamples) noexcept
|
||||
{
|
||||
jassert (samples != nullptr);
|
||||
|
||||
if (shouldUpdateDamping)
|
||||
updateDamping();
|
||||
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
{
|
||||
const float input = samples[i] * gain;
|
||||
float output = 0;
|
||||
|
||||
for (int j = 0; j < numCombs; ++j) // accumulate the comb filters in parallel
|
||||
output += comb[0][j].process (input);
|
||||
|
||||
for (int j = 0; j < numAllPasses; ++j) // run the allpass filters in series
|
||||
output = allPass[0][j].process (output);
|
||||
|
||||
samples[i] = output * wet1 + 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
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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"
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_AUDIO_BASICS_H_INCLUDED
|
||||
#define JUCE_AUDIO_BASICS_H_INCLUDED
|
||||
|
||||
#include "../juce_core/juce_core.h"
|
||||
|
||||
//=============================================================================
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#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
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#include "juce_audio_basics.cpp"
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"id": "juce_audio_basics",
|
||||
"name": "JUCE audio and midi data classes",
|
||||
"version": "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"
|
||||
}
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,428 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
MidiKeyboardState::MidiKeyboardState()
|
||||
{
|
||||
zerostruct (noteStates);
|
||||
}
|
||||
|
||||
MidiKeyboardState::~MidiKeyboardState()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MidiKeyboardState::reset()
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
zerostruct (noteStates);
|
||||
eventsToAdd.clear();
|
||||
}
|
||||
|
||||
bool MidiKeyboardState::isNoteOn (const int midiChannel, const int n) const noexcept
|
||||
{
|
||||
jassert (midiChannel >= 0 && midiChannel <= 16);
|
||||
|
||||
return isPositiveAndBelow (n, (int) 128)
|
||||
&& (noteStates[n] & (1 << (midiChannel - 1))) != 0;
|
||||
}
|
||||
|
||||
bool MidiKeyboardState::isNoteOnForChannels (const int midiChannelMask, const int n) const noexcept
|
||||
{
|
||||
return isPositiveAndBelow (n, (int) 128)
|
||||
&& (noteStates[n] & midiChannelMask) != 0;
|
||||
}
|
||||
|
||||
void MidiKeyboardState::noteOn (const int midiChannel, const int midiNoteNumber, const float velocity)
|
||||
{
|
||||
jassert (midiChannel >= 0 && midiChannel <= 16);
|
||||
jassert (isPositiveAndBelow (midiNoteNumber, (int) 128));
|
||||
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
if (isPositiveAndBelow (midiNoteNumber, (int) 128))
|
||||
{
|
||||
const int timeNow = (int) Time::getMillisecondCounter();
|
||||
eventsToAdd.addEvent (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity), timeNow);
|
||||
eventsToAdd.clear (0, timeNow - 500);
|
||||
|
||||
noteOnInternal (midiChannel, midiNoteNumber, velocity);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardState::noteOnInternal (const int midiChannel, const int midiNoteNumber, const float velocity)
|
||||
{
|
||||
if (isPositiveAndBelow (midiNoteNumber, (int) 128))
|
||||
{
|
||||
noteStates [midiNoteNumber] |= (1 << (midiChannel - 1));
|
||||
|
||||
for (int i = listeners.size(); --i >= 0;)
|
||||
listeners.getUnchecked(i)->handleNoteOn (this, midiChannel, midiNoteNumber, velocity);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardState::noteOff (const int midiChannel, const int midiNoteNumber)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
if (isNoteOn (midiChannel, midiNoteNumber))
|
||||
{
|
||||
const int timeNow = (int) Time::getMillisecondCounter();
|
||||
eventsToAdd.addEvent (MidiMessage::noteOff (midiChannel, midiNoteNumber), timeNow);
|
||||
eventsToAdd.clear (0, timeNow - 500);
|
||||
|
||||
noteOffInternal (midiChannel, midiNoteNumber);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardState::noteOffInternal (const int midiChannel, const int midiNoteNumber)
|
||||
{
|
||||
if (isNoteOn (midiChannel, midiNoteNumber))
|
||||
{
|
||||
noteStates [midiNoteNumber] &= ~(1 << (midiChannel - 1));
|
||||
|
||||
for (int i = listeners.size(); --i >= 0;)
|
||||
listeners.getUnchecked(i)->handleNoteOff (this, midiChannel, midiNoteNumber);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardState::allNotesOff (const int midiChannel)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
if (midiChannel <= 0)
|
||||
{
|
||||
for (int i = 1; i <= 16; ++i)
|
||||
allNotesOff (i);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < 128; ++i)
|
||||
noteOff (midiChannel, i);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardState::processNextMidiEvent (const MidiMessage& message)
|
||||
{
|
||||
if (message.isNoteOn())
|
||||
{
|
||||
noteOnInternal (message.getChannel(), message.getNoteNumber(), message.getFloatVelocity());
|
||||
}
|
||||
else if (message.isNoteOff())
|
||||
{
|
||||
noteOffInternal (message.getChannel(), message.getNoteNumber());
|
||||
}
|
||||
else if (message.isAllNotesOff())
|
||||
{
|
||||
for (int i = 0; i < 128; ++i)
|
||||
noteOffInternal (message.getChannel(), i);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardState::processNextMidiBuffer (MidiBuffer& buffer,
|
||||
const int startSample,
|
||||
const int numSamples,
|
||||
const bool injectIndirectEvents)
|
||||
{
|
||||
MidiBuffer::Iterator i (buffer);
|
||||
MidiMessage message (0xf4, 0.0);
|
||||
int time;
|
||||
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
while (i.getNextEvent (message, time))
|
||||
processNextMidiEvent (message);
|
||||
|
||||
if (injectIndirectEvents)
|
||||
{
|
||||
MidiBuffer::Iterator i2 (eventsToAdd);
|
||||
const int firstEventToAdd = eventsToAdd.getFirstEventTime();
|
||||
const double scaleFactor = numSamples / (double) (eventsToAdd.getLastEventTime() + 1 - firstEventToAdd);
|
||||
|
||||
while (i2.getNextEvent (message, time))
|
||||
{
|
||||
const int pos = jlimit (0, numSamples - 1, roundToInt ((time - firstEventToAdd) * scaleFactor));
|
||||
buffer.addEvent (message, startSample + pos);
|
||||
}
|
||||
}
|
||||
|
||||
eventsToAdd.clear();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MidiKeyboardState::addListener (MidiKeyboardStateListener* const listener)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
listeners.addIfNotAlreadyThere (listener);
|
||||
}
|
||||
|
||||
void MidiKeyboardState::removeListener (MidiKeyboardStateListener* const listener)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
listeners.removeFirstMatchingValue (listener);
|
||||
}
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,903 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,331 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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()
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,265 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,259 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
IIRFilterAudioSource::IIRFilterAudioSource (AudioSource* const inputSource,
|
||||
const bool deleteInputWhenDeleted)
|
||||
: input (inputSource, deleteInputWhenDeleted)
|
||||
{
|
||||
jassert (inputSource != nullptr);
|
||||
|
||||
for (int i = 2; --i >= 0;)
|
||||
iirFilters.add (new IIRFilter());
|
||||
}
|
||||
|
||||
IIRFilterAudioSource::~IIRFilterAudioSource() {}
|
||||
|
||||
//==============================================================================
|
||||
void IIRFilterAudioSource::setCoefficients (const IIRCoefficients& newCoefficients)
|
||||
{
|
||||
for (int i = iirFilters.size(); --i >= 0;)
|
||||
iirFilters.getUnchecked(i)->setCoefficients (newCoefficients);
|
||||
}
|
||||
|
||||
void IIRFilterAudioSource::makeInactive()
|
||||
{
|
||||
for (int i = iirFilters.size(); --i >= 0;)
|
||||
iirFilters.getUnchecked(i)->makeInactive();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void IIRFilterAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate)
|
||||
{
|
||||
input->prepareToPlay (samplesPerBlockExpected, sampleRate);
|
||||
|
||||
for (int i = iirFilters.size(); --i >= 0;)
|
||||
iirFilters.getUnchecked(i)->reset();
|
||||
}
|
||||
|
||||
void IIRFilterAudioSource::releaseResources()
|
||||
{
|
||||
input->releaseResources();
|
||||
}
|
||||
|
||||
void IIRFilterAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill)
|
||||
{
|
||||
input->getNextAudioBlock (bufferToFill);
|
||||
|
||||
const int numChannels = bufferToFill.buffer->getNumChannels();
|
||||
|
||||
while (numChannels > iirFilters.size())
|
||||
iirFilters.add (new IIRFilter (*iirFilters.getUnchecked (0)));
|
||||
|
||||
for (int i = 0; i < numChannels; ++i)
|
||||
iirFilters.getUnchecked(i)
|
||||
->processSamples (bufferToFill.buffer->getWritePointer (i, bufferToFill.startSample),
|
||||
bufferToFill.numSamples);
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,261 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
ReverbAudioSource::ReverbAudioSource (AudioSource* const inputSource, const bool deleteInputWhenDeleted)
|
||||
: input (inputSource, deleteInputWhenDeleted),
|
||||
bypass (false)
|
||||
{
|
||||
jassert (inputSource != nullptr);
|
||||
}
|
||||
|
||||
ReverbAudioSource::~ReverbAudioSource() {}
|
||||
|
||||
void ReverbAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
input->prepareToPlay (samplesPerBlockExpected, sampleRate);
|
||||
reverb.setSampleRate (sampleRate);
|
||||
}
|
||||
|
||||
void ReverbAudioSource::releaseResources() {}
|
||||
|
||||
void ReverbAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
input->getNextAudioBlock (bufferToFill);
|
||||
|
||||
if (! bypass)
|
||||
{
|
||||
float* const firstChannel = bufferToFill.buffer->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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#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
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_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
|
||||
|
|
@ -0,0 +1,503 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,557 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue