@@ -16,6 +16,8 @@ lv2: | |||||
$(MAKE) -C cabbage/LV2-midi | $(MAKE) -C cabbage/LV2-midi | ||||
$(MAKE) -C protoplug/LV2-fx | $(MAKE) -C protoplug/LV2-fx | ||||
$(MAKE) -C protoplug/LV2-gen | $(MAKE) -C protoplug/LV2-gen | ||||
$(MAKE) -C radium-compressor/LV2-mono | |||||
$(MAKE) -C radium-compressor/LV2-stereo | |||||
# ----------------------------------------- | # ----------------------------------------- | ||||
# vst | # vst | ||||
@@ -28,6 +30,8 @@ vst: | |||||
$(MAKE) -C cabbage/VST-midi | $(MAKE) -C cabbage/VST-midi | ||||
$(MAKE) -C protoplug/VST-fx | $(MAKE) -C protoplug/VST-fx | ||||
$(MAKE) -C protoplug/VST-gen | $(MAKE) -C protoplug/VST-gen | ||||
$(MAKE) -C radium-compressor/VST-mono | |||||
$(MAKE) -C radium-compressor/VST-stereo | |||||
# ----------------------------------------- | # ----------------------------------------- | ||||
# clean | # clean | ||||
@@ -40,6 +44,8 @@ clean: | |||||
$(MAKE) clean -C cabbage/LV2-midi | $(MAKE) clean -C cabbage/LV2-midi | ||||
$(MAKE) clean -C protoplug/LV2-fx | $(MAKE) clean -C protoplug/LV2-fx | ||||
$(MAKE) clean -C protoplug/LV2-gen | $(MAKE) clean -C protoplug/LV2-gen | ||||
$(MAKE) clean -C radium-compressor/LV2-mono | |||||
$(MAKE) clean -C radium-compressor/LV2-stereo | |||||
# VST | # VST | ||||
$(MAKE) clean -C argotlunar/VST | $(MAKE) clean -C argotlunar/VST | ||||
@@ -48,6 +54,8 @@ clean: | |||||
$(MAKE) clean -C cabbage/VST-midi | $(MAKE) clean -C cabbage/VST-midi | ||||
$(MAKE) clean -C protoplug/VST-fx | $(MAKE) clean -C protoplug/VST-fx | ||||
$(MAKE) clean -C protoplug/VST-gen | $(MAKE) clean -C protoplug/VST-gen | ||||
$(MAKE) clean -C radium-compressor/VST-mono | |||||
$(MAKE) clean -C radium-compressor/VST-stereo | |||||
rm -rf */LV2/intermediate | rm -rf */LV2/intermediate | ||||
rm -rf */VST/intermediate | rm -rf */VST/intermediate | ||||
@@ -0,0 +1,12 @@ | |||||
Important Note!! | |||||
================ | |||||
The purpose of this folder is to contain files that are auto-generated by the Introjucer, | |||||
and ALL files in this folder will be mercilessly DELETED and completely re-written whenever | |||||
the Introjucer saves your project. | |||||
Therefore, it's a bad idea to make any manual changes to the files in here, or to | |||||
put any of your own files in here if you don't want to lose them. (Of course you may choose | |||||
to add the folder's contents to your version-control system so that you can re-merge your own | |||||
modifications after the Introjucer has saved its changes). |
@@ -0,0 +1,601 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
void AudioDataConverters::convertFloatToInt16LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) | |||||
{ | |||||
const double maxVal = (double) 0x7fff; | |||||
char* intData = static_cast <char*> (dest); | |||||
if (dest != (void*) source || destBytesPerSample <= 4) | |||||
{ | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
*(uint16*) intData = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
intData += destBytesPerSample; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
intData += destBytesPerSample * numSamples; | |||||
for (int i = numSamples; --i >= 0;) | |||||
{ | |||||
intData -= destBytesPerSample; | |||||
*(uint16*) intData = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
} | |||||
} | |||||
} | |||||
void AudioDataConverters::convertFloatToInt16BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) | |||||
{ | |||||
const double maxVal = (double) 0x7fff; | |||||
char* intData = static_cast <char*> (dest); | |||||
if (dest != (void*) source || destBytesPerSample <= 4) | |||||
{ | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
*(uint16*) intData = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
intData += destBytesPerSample; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
intData += destBytesPerSample * numSamples; | |||||
for (int i = numSamples; --i >= 0;) | |||||
{ | |||||
intData -= destBytesPerSample; | |||||
*(uint16*) intData = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
} | |||||
} | |||||
} | |||||
void AudioDataConverters::convertFloatToInt24LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) | |||||
{ | |||||
const double maxVal = (double) 0x7fffff; | |||||
char* intData = static_cast <char*> (dest); | |||||
if (dest != (void*) source || destBytesPerSample <= 4) | |||||
{ | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
ByteOrder::littleEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); | |||||
intData += destBytesPerSample; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
intData += destBytesPerSample * numSamples; | |||||
for (int i = numSamples; --i >= 0;) | |||||
{ | |||||
intData -= destBytesPerSample; | |||||
ByteOrder::littleEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); | |||||
} | |||||
} | |||||
} | |||||
void AudioDataConverters::convertFloatToInt24BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) | |||||
{ | |||||
const double maxVal = (double) 0x7fffff; | |||||
char* intData = static_cast <char*> (dest); | |||||
if (dest != (void*) source || destBytesPerSample <= 4) | |||||
{ | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
ByteOrder::bigEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); | |||||
intData += destBytesPerSample; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
intData += destBytesPerSample * numSamples; | |||||
for (int i = numSamples; --i >= 0;) | |||||
{ | |||||
intData -= destBytesPerSample; | |||||
ByteOrder::bigEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); | |||||
} | |||||
} | |||||
} | |||||
void AudioDataConverters::convertFloatToInt32LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) | |||||
{ | |||||
const double maxVal = (double) 0x7fffffff; | |||||
char* intData = static_cast <char*> (dest); | |||||
if (dest != (void*) source || destBytesPerSample <= 4) | |||||
{ | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
*(uint32*)intData = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
intData += destBytesPerSample; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
intData += destBytesPerSample * numSamples; | |||||
for (int i = numSamples; --i >= 0;) | |||||
{ | |||||
intData -= destBytesPerSample; | |||||
*(uint32*)intData = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
} | |||||
} | |||||
} | |||||
void AudioDataConverters::convertFloatToInt32BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) | |||||
{ | |||||
const double maxVal = (double) 0x7fffffff; | |||||
char* intData = static_cast <char*> (dest); | |||||
if (dest != (void*) source || destBytesPerSample <= 4) | |||||
{ | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
*(uint32*)intData = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
intData += destBytesPerSample; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
intData += destBytesPerSample * numSamples; | |||||
for (int i = numSamples; --i >= 0;) | |||||
{ | |||||
intData -= destBytesPerSample; | |||||
*(uint32*)intData = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
} | |||||
} | |||||
} | |||||
void AudioDataConverters::convertFloatToFloat32LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) | |||||
{ | |||||
jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data! | |||||
char* d = static_cast <char*> (dest); | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
*(float*) d = source[i]; | |||||
#if JUCE_BIG_ENDIAN | |||||
*(uint32*) d = ByteOrder::swap (*(uint32*) d); | |||||
#endif | |||||
d += destBytesPerSample; | |||||
} | |||||
} | |||||
void AudioDataConverters::convertFloatToFloat32BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) | |||||
{ | |||||
jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data! | |||||
char* d = static_cast <char*> (dest); | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
*(float*) d = source[i]; | |||||
#if JUCE_LITTLE_ENDIAN | |||||
*(uint32*) d = ByteOrder::swap (*(uint32*) d); | |||||
#endif | |||||
d += destBytesPerSample; | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void AudioDataConverters::convertInt16LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) | |||||
{ | |||||
const float scale = 1.0f / 0x7fff; | |||||
const char* intData = static_cast <const char*> (source); | |||||
if (source != (void*) dest || srcBytesPerSample >= 4) | |||||
{ | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*(uint16*)intData); | |||||
intData += srcBytesPerSample; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
intData += srcBytesPerSample * numSamples; | |||||
for (int i = numSamples; --i >= 0;) | |||||
{ | |||||
intData -= srcBytesPerSample; | |||||
dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*(uint16*)intData); | |||||
} | |||||
} | |||||
} | |||||
void AudioDataConverters::convertInt16BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) | |||||
{ | |||||
const float scale = 1.0f / 0x7fff; | |||||
const char* intData = static_cast <const char*> (source); | |||||
if (source != (void*) dest || srcBytesPerSample >= 4) | |||||
{ | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*(uint16*)intData); | |||||
intData += srcBytesPerSample; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
intData += srcBytesPerSample * numSamples; | |||||
for (int i = numSamples; --i >= 0;) | |||||
{ | |||||
intData -= srcBytesPerSample; | |||||
dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*(uint16*)intData); | |||||
} | |||||
} | |||||
} | |||||
void AudioDataConverters::convertInt24LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) | |||||
{ | |||||
const float scale = 1.0f / 0x7fffff; | |||||
const char* intData = static_cast <const char*> (source); | |||||
if (source != (void*) dest || srcBytesPerSample >= 4) | |||||
{ | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
dest[i] = scale * (short) ByteOrder::littleEndian24Bit (intData); | |||||
intData += srcBytesPerSample; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
intData += srcBytesPerSample * numSamples; | |||||
for (int i = numSamples; --i >= 0;) | |||||
{ | |||||
intData -= srcBytesPerSample; | |||||
dest[i] = scale * (short) ByteOrder::littleEndian24Bit (intData); | |||||
} | |||||
} | |||||
} | |||||
void AudioDataConverters::convertInt24BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) | |||||
{ | |||||
const float scale = 1.0f / 0x7fffff; | |||||
const char* intData = static_cast <const char*> (source); | |||||
if (source != (void*) dest || srcBytesPerSample >= 4) | |||||
{ | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
dest[i] = scale * (short) ByteOrder::bigEndian24Bit (intData); | |||||
intData += srcBytesPerSample; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
intData += srcBytesPerSample * numSamples; | |||||
for (int i = numSamples; --i >= 0;) | |||||
{ | |||||
intData -= srcBytesPerSample; | |||||
dest[i] = scale * (short) ByteOrder::bigEndian24Bit (intData); | |||||
} | |||||
} | |||||
} | |||||
void AudioDataConverters::convertInt32LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) | |||||
{ | |||||
const float scale = 1.0f / 0x7fffffff; | |||||
const char* intData = static_cast <const char*> (source); | |||||
if (source != (void*) dest || srcBytesPerSample >= 4) | |||||
{ | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
dest[i] = scale * (int) ByteOrder::swapIfBigEndian (*(uint32*) intData); | |||||
intData += srcBytesPerSample; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
intData += srcBytesPerSample * numSamples; | |||||
for (int i = numSamples; --i >= 0;) | |||||
{ | |||||
intData -= srcBytesPerSample; | |||||
dest[i] = scale * (int) ByteOrder::swapIfBigEndian (*(uint32*) intData); | |||||
} | |||||
} | |||||
} | |||||
void AudioDataConverters::convertInt32BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) | |||||
{ | |||||
const float scale = 1.0f / 0x7fffffff; | |||||
const char* intData = static_cast <const char*> (source); | |||||
if (source != (void*) dest || srcBytesPerSample >= 4) | |||||
{ | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
dest[i] = scale * (int) ByteOrder::swapIfLittleEndian (*(uint32*) intData); | |||||
intData += srcBytesPerSample; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
intData += srcBytesPerSample * numSamples; | |||||
for (int i = numSamples; --i >= 0;) | |||||
{ | |||||
intData -= srcBytesPerSample; | |||||
dest[i] = scale * (int) ByteOrder::swapIfLittleEndian (*(uint32*) intData); | |||||
} | |||||
} | |||||
} | |||||
void AudioDataConverters::convertFloat32LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) | |||||
{ | |||||
const char* s = static_cast <const char*> (source); | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
dest[i] = *(float*)s; | |||||
#if JUCE_BIG_ENDIAN | |||||
uint32* const d = (uint32*) (dest + i); | |||||
*d = ByteOrder::swap (*d); | |||||
#endif | |||||
s += srcBytesPerSample; | |||||
} | |||||
} | |||||
void AudioDataConverters::convertFloat32BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) | |||||
{ | |||||
const char* s = static_cast <const char*> (source); | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
dest[i] = *(float*)s; | |||||
#if JUCE_LITTLE_ENDIAN | |||||
uint32* const d = (uint32*) (dest + i); | |||||
*d = ByteOrder::swap (*d); | |||||
#endif | |||||
s += srcBytesPerSample; | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void AudioDataConverters::convertFloatToFormat (const DataFormat destFormat, | |||||
const float* const source, | |||||
void* const dest, | |||||
const int numSamples) | |||||
{ | |||||
switch (destFormat) | |||||
{ | |||||
case int16LE: convertFloatToInt16LE (source, dest, numSamples); break; | |||||
case int16BE: convertFloatToInt16BE (source, dest, numSamples); break; | |||||
case int24LE: convertFloatToInt24LE (source, dest, numSamples); break; | |||||
case int24BE: convertFloatToInt24BE (source, dest, numSamples); break; | |||||
case int32LE: convertFloatToInt32LE (source, dest, numSamples); break; | |||||
case int32BE: convertFloatToInt32BE (source, dest, numSamples); break; | |||||
case float32LE: convertFloatToFloat32LE (source, dest, numSamples); break; | |||||
case float32BE: convertFloatToFloat32BE (source, dest, numSamples); break; | |||||
default: jassertfalse; break; | |||||
} | |||||
} | |||||
void AudioDataConverters::convertFormatToFloat (const DataFormat sourceFormat, | |||||
const void* const source, | |||||
float* const dest, | |||||
const int numSamples) | |||||
{ | |||||
switch (sourceFormat) | |||||
{ | |||||
case int16LE: convertInt16LEToFloat (source, dest, numSamples); break; | |||||
case int16BE: convertInt16BEToFloat (source, dest, numSamples); break; | |||||
case int24LE: convertInt24LEToFloat (source, dest, numSamples); break; | |||||
case int24BE: convertInt24BEToFloat (source, dest, numSamples); break; | |||||
case int32LE: convertInt32LEToFloat (source, dest, numSamples); break; | |||||
case int32BE: convertInt32BEToFloat (source, dest, numSamples); break; | |||||
case float32LE: convertFloat32LEToFloat (source, dest, numSamples); break; | |||||
case float32BE: convertFloat32BEToFloat (source, dest, numSamples); break; | |||||
default: jassertfalse; break; | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void AudioDataConverters::interleaveSamples (const float** const source, | |||||
float* const dest, | |||||
const int numSamples, | |||||
const int numChannels) | |||||
{ | |||||
for (int chan = 0; chan < numChannels; ++chan) | |||||
{ | |||||
int i = chan; | |||||
const float* src = source [chan]; | |||||
for (int j = 0; j < numSamples; ++j) | |||||
{ | |||||
dest [i] = src [j]; | |||||
i += numChannels; | |||||
} | |||||
} | |||||
} | |||||
void AudioDataConverters::deinterleaveSamples (const float* const source, | |||||
float** const dest, | |||||
const int numSamples, | |||||
const int numChannels) | |||||
{ | |||||
for (int chan = 0; chan < numChannels; ++chan) | |||||
{ | |||||
int i = chan; | |||||
float* dst = dest [chan]; | |||||
for (int j = 0; j < numSamples; ++j) | |||||
{ | |||||
dst [j] = source [i]; | |||||
i += numChannels; | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
#if JUCE_UNIT_TESTS | |||||
class AudioConversionTests : public UnitTest | |||||
{ | |||||
public: | |||||
AudioConversionTests() : UnitTest ("Audio data conversion") {} | |||||
template <class F1, class E1, class F2, class E2> | |||||
struct Test5 | |||||
{ | |||||
static void test (UnitTest& unitTest) | |||||
{ | |||||
test (unitTest, false); | |||||
test (unitTest, true); | |||||
} | |||||
static void test (UnitTest& unitTest, bool inPlace) | |||||
{ | |||||
const int numSamples = 2048; | |||||
int32 original [numSamples], converted [numSamples], reversed [numSamples]; | |||||
Random r; | |||||
{ | |||||
AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::NonConst> d (original); | |||||
bool clippingFailed = false; | |||||
for (int i = 0; i < numSamples / 2; ++i) | |||||
{ | |||||
d.setAsFloat (r.nextFloat() * 2.2f - 1.1f); | |||||
if (! d.isFloatingPoint()) | |||||
clippingFailed = d.getAsFloat() > 1.0f || d.getAsFloat() < -1.0f || clippingFailed; | |||||
++d; | |||||
d.setAsInt32 (r.nextInt()); | |||||
++d; | |||||
} | |||||
unitTest.expect (! clippingFailed); | |||||
} | |||||
// convert data from the source to dest format.. | |||||
ScopedPointer<AudioData::Converter> conv (new AudioData::ConverterInstance <AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const>, | |||||
AudioData::Pointer<F2, E2, AudioData::NonInterleaved, AudioData::NonConst> >()); | |||||
conv->convertSamples (inPlace ? reversed : converted, original, numSamples); | |||||
// ..and back again.. | |||||
conv = new AudioData::ConverterInstance <AudioData::Pointer<F2, E2, AudioData::NonInterleaved, AudioData::Const>, | |||||
AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::NonConst> >(); | |||||
if (! inPlace) | |||||
zeromem (reversed, sizeof (reversed)); | |||||
conv->convertSamples (reversed, inPlace ? reversed : converted, numSamples); | |||||
{ | |||||
int biggestDiff = 0; | |||||
AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const> d1 (original); | |||||
AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const> d2 (reversed); | |||||
const int errorMargin = 2 * AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const>::get32BitResolution() | |||||
+ AudioData::Pointer<F2, E2, AudioData::NonInterleaved, AudioData::Const>::get32BitResolution(); | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
biggestDiff = jmax (biggestDiff, std::abs (d1.getAsInt32() - d2.getAsInt32())); | |||||
++d1; | |||||
++d2; | |||||
} | |||||
unitTest.expect (biggestDiff <= errorMargin); | |||||
} | |||||
} | |||||
}; | |||||
template <class F1, class E1, class FormatType> | |||||
struct Test3 | |||||
{ | |||||
static void test (UnitTest& unitTest) | |||||
{ | |||||
Test5 <F1, E1, FormatType, AudioData::BigEndian>::test (unitTest); | |||||
Test5 <F1, E1, FormatType, AudioData::LittleEndian>::test (unitTest); | |||||
} | |||||
}; | |||||
template <class FormatType, class Endianness> | |||||
struct Test2 | |||||
{ | |||||
static void test (UnitTest& unitTest) | |||||
{ | |||||
Test3 <FormatType, Endianness, AudioData::Int8>::test (unitTest); | |||||
Test3 <FormatType, Endianness, AudioData::UInt8>::test (unitTest); | |||||
Test3 <FormatType, Endianness, AudioData::Int16>::test (unitTest); | |||||
Test3 <FormatType, Endianness, AudioData::Int24>::test (unitTest); | |||||
Test3 <FormatType, Endianness, AudioData::Int32>::test (unitTest); | |||||
Test3 <FormatType, Endianness, AudioData::Float32>::test (unitTest); | |||||
} | |||||
}; | |||||
template <class FormatType> | |||||
struct Test1 | |||||
{ | |||||
static void test (UnitTest& unitTest) | |||||
{ | |||||
Test2 <FormatType, AudioData::BigEndian>::test (unitTest); | |||||
Test2 <FormatType, AudioData::LittleEndian>::test (unitTest); | |||||
} | |||||
}; | |||||
void runTest() | |||||
{ | |||||
beginTest ("Round-trip conversion: Int8"); | |||||
Test1 <AudioData::Int8>::test (*this); | |||||
beginTest ("Round-trip conversion: Int16"); | |||||
Test1 <AudioData::Int16>::test (*this); | |||||
beginTest ("Round-trip conversion: Int24"); | |||||
Test1 <AudioData::Int24>::test (*this); | |||||
beginTest ("Round-trip conversion: Int32"); | |||||
Test1 <AudioData::Int32>::test (*this); | |||||
beginTest ("Round-trip conversion: Float32"); | |||||
Test1 <AudioData::Float32>::test (*this); | |||||
} | |||||
}; | |||||
static AudioConversionTests audioConversionUnitTests; | |||||
#endif |
@@ -0,0 +1,644 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_AUDIODATACONVERTERS_JUCEHEADER__ | |||||
#define __JUCE_AUDIODATACONVERTERS_JUCEHEADER__ | |||||
//============================================================================== | |||||
/** | |||||
This class a container which holds all the classes pertaining to the AudioData::Pointer | |||||
audio sample format class. | |||||
@see AudioData::Pointer. | |||||
*/ | |||||
class JUCE_API AudioData | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
// These types can be used as the SampleFormat template parameter for the AudioData::Pointer class. | |||||
class Int8; /**< Used as a template parameter for AudioData::Pointer. Indicates an 8-bit integer packed data format. */ | |||||
class UInt8; /**< Used as a template parameter for AudioData::Pointer. Indicates an 8-bit unsigned integer packed data format. */ | |||||
class Int16; /**< Used as a template parameter for AudioData::Pointer. Indicates an 16-bit integer packed data format. */ | |||||
class Int24; /**< Used as a template parameter for AudioData::Pointer. Indicates an 24-bit integer packed data format. */ | |||||
class Int32; /**< Used as a template parameter for AudioData::Pointer. Indicates an 32-bit integer packed data format. */ | |||||
class Float32; /**< Used as a template parameter for AudioData::Pointer. Indicates an 32-bit float data format. */ | |||||
//============================================================================== | |||||
// These types can be used as the Endianness template parameter for the AudioData::Pointer class. | |||||
class BigEndian; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored in big-endian order. */ | |||||
class LittleEndian; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored in little-endian order. */ | |||||
class NativeEndian; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored in the CPU's native endianness. */ | |||||
//============================================================================== | |||||
// These types can be used as the InterleavingType template parameter for the AudioData::Pointer class. | |||||
class NonInterleaved; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored contiguously. */ | |||||
class Interleaved; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are interleaved with a number of other channels. */ | |||||
//============================================================================== | |||||
// These types can be used as the Constness template parameter for the AudioData::Pointer class. | |||||
class NonConst; /**< Used as a template parameter for AudioData::Pointer. Indicates that the pointer can be used for non-const data. */ | |||||
class Const; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples can only be used for const data.. */ | |||||
#ifndef DOXYGEN | |||||
//============================================================================== | |||||
class BigEndian | |||||
{ | |||||
public: | |||||
template <class SampleFormatType> static inline float getAsFloat (SampleFormatType& s) noexcept { return s.getAsFloatBE(); } | |||||
template <class SampleFormatType> static inline void setAsFloat (SampleFormatType& s, float newValue) noexcept { s.setAsFloatBE (newValue); } | |||||
template <class SampleFormatType> static inline int32 getAsInt32 (SampleFormatType& s) noexcept { return s.getAsInt32BE(); } | |||||
template <class SampleFormatType> static inline void setAsInt32 (SampleFormatType& s, int32 newValue) noexcept { s.setAsInt32BE (newValue); } | |||||
template <class SourceType, class DestType> static inline void copyFrom (DestType& dest, SourceType& source) noexcept { dest.copyFromBE (source); } | |||||
enum { isBigEndian = 1 }; | |||||
}; | |||||
class LittleEndian | |||||
{ | |||||
public: | |||||
template <class SampleFormatType> static inline float getAsFloat (SampleFormatType& s) noexcept { return s.getAsFloatLE(); } | |||||
template <class SampleFormatType> static inline void setAsFloat (SampleFormatType& s, float newValue) noexcept { s.setAsFloatLE (newValue); } | |||||
template <class SampleFormatType> static inline int32 getAsInt32 (SampleFormatType& s) noexcept { return s.getAsInt32LE(); } | |||||
template <class SampleFormatType> static inline void setAsInt32 (SampleFormatType& s, int32 newValue) noexcept { s.setAsInt32LE (newValue); } | |||||
template <class SourceType, class DestType> static inline void copyFrom (DestType& dest, SourceType& source) noexcept { dest.copyFromLE (source); } | |||||
enum { isBigEndian = 0 }; | |||||
}; | |||||
#if JUCE_BIG_ENDIAN | |||||
class NativeEndian : public BigEndian {}; | |||||
#else | |||||
class NativeEndian : public LittleEndian {}; | |||||
#endif | |||||
//============================================================================== | |||||
class Int8 | |||||
{ | |||||
public: | |||||
inline Int8 (void* d) noexcept : data (static_cast <int8*> (d)) {} | |||||
inline void advance() noexcept { ++data; } | |||||
inline void skip (int numSamples) noexcept { data += numSamples; } | |||||
inline float getAsFloatLE() const noexcept { return (float) (*data * (1.0 / (1.0 + maxValue))); } | |||||
inline float getAsFloatBE() const noexcept { return getAsFloatLE(); } | |||||
inline void setAsFloatLE (float newValue) noexcept { *data = (int8) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + maxValue))); } | |||||
inline void setAsFloatBE (float newValue) noexcept { setAsFloatLE (newValue); } | |||||
inline int32 getAsInt32LE() const noexcept { return (int) (*data << 24); } | |||||
inline int32 getAsInt32BE() const noexcept { return getAsInt32LE(); } | |||||
inline void setAsInt32LE (int newValue) noexcept { *data = (int8) (newValue >> 24); } | |||||
inline void setAsInt32BE (int newValue) noexcept { setAsInt32LE (newValue); } | |||||
inline void clear() noexcept { *data = 0; } | |||||
inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} | |||||
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } | |||||
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } | |||||
inline void copyFromSameType (Int8& source) noexcept { *data = *source.data; } | |||||
int8* data; | |||||
enum { bytesPerSample = 1, maxValue = 0x7f, resolution = (1 << 24), isFloat = 0 }; | |||||
}; | |||||
class UInt8 | |||||
{ | |||||
public: | |||||
inline UInt8 (void* d) noexcept : data (static_cast <uint8*> (d)) {} | |||||
inline void advance() noexcept { ++data; } | |||||
inline void skip (int numSamples) noexcept { data += numSamples; } | |||||
inline float getAsFloatLE() const noexcept { return (float) ((*data - 128) * (1.0 / (1.0 + maxValue))); } | |||||
inline float getAsFloatBE() const noexcept { return getAsFloatLE(); } | |||||
inline void setAsFloatLE (float newValue) noexcept { *data = (uint8) jlimit (0, 255, 128 + roundToInt (newValue * (1.0 + maxValue))); } | |||||
inline void setAsFloatBE (float newValue) noexcept { setAsFloatLE (newValue); } | |||||
inline int32 getAsInt32LE() const noexcept { return (int) ((*data - 128) << 24); } | |||||
inline int32 getAsInt32BE() const noexcept { return getAsInt32LE(); } | |||||
inline void setAsInt32LE (int newValue) noexcept { *data = (uint8) (128 + (newValue >> 24)); } | |||||
inline void setAsInt32BE (int newValue) noexcept { setAsInt32LE (newValue); } | |||||
inline void clear() noexcept { *data = 128; } | |||||
inline void clearMultiple (int num) noexcept { memset (data, 128, (size_t) num) ;} | |||||
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } | |||||
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } | |||||
inline void copyFromSameType (UInt8& source) noexcept { *data = *source.data; } | |||||
uint8* data; | |||||
enum { bytesPerSample = 1, maxValue = 0x7f, resolution = (1 << 24), isFloat = 0 }; | |||||
}; | |||||
class Int16 | |||||
{ | |||||
public: | |||||
inline Int16 (void* d) noexcept : data (static_cast <uint16*> (d)) {} | |||||
inline void advance() noexcept { ++data; } | |||||
inline void skip (int numSamples) noexcept { data += numSamples; } | |||||
inline float getAsFloatLE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int16) ByteOrder::swapIfBigEndian (*data)); } | |||||
inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int16) ByteOrder::swapIfLittleEndian (*data)); } | |||||
inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint16) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + maxValue)))); } | |||||
inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint16) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + maxValue)))); } | |||||
inline int32 getAsInt32LE() const noexcept { return (int32) (ByteOrder::swapIfBigEndian ((uint16) *data) << 16); } | |||||
inline int32 getAsInt32BE() const noexcept { return (int32) (ByteOrder::swapIfLittleEndian ((uint16) *data) << 16); } | |||||
inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint16) (newValue >> 16)); } | |||||
inline void setAsInt32BE (int32 newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint16) (newValue >> 16)); } | |||||
inline void clear() noexcept { *data = 0; } | |||||
inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} | |||||
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } | |||||
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } | |||||
inline void copyFromSameType (Int16& source) noexcept { *data = *source.data; } | |||||
uint16* data; | |||||
enum { bytesPerSample = 2, maxValue = 0x7fff, resolution = (1 << 16), isFloat = 0 }; | |||||
}; | |||||
class Int24 | |||||
{ | |||||
public: | |||||
inline Int24 (void* d) noexcept : data (static_cast <char*> (d)) {} | |||||
inline void advance() noexcept { data += 3; } | |||||
inline void skip (int numSamples) noexcept { data += 3 * numSamples; } | |||||
inline float getAsFloatLE() const noexcept { return (float) (ByteOrder::littleEndian24Bit (data) * (1.0 / (1.0 + maxValue))); } | |||||
inline float getAsFloatBE() const noexcept { return (float) (ByteOrder::bigEndian24Bit (data) * (1.0 / (1.0 + maxValue))); } | |||||
inline void setAsFloatLE (float newValue) noexcept { ByteOrder::littleEndian24BitToChars (jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + maxValue))), data); } | |||||
inline void setAsFloatBE (float newValue) noexcept { ByteOrder::bigEndian24BitToChars (jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + maxValue))), data); } | |||||
inline int32 getAsInt32LE() const noexcept { return (int32) ByteOrder::littleEndian24Bit (data) << 8; } | |||||
inline int32 getAsInt32BE() const noexcept { return (int32) ByteOrder::bigEndian24Bit (data) << 8; } | |||||
inline void setAsInt32LE (int32 newValue) noexcept { ByteOrder::littleEndian24BitToChars (newValue >> 8, data); } | |||||
inline void setAsInt32BE (int32 newValue) noexcept { ByteOrder::bigEndian24BitToChars (newValue >> 8, data); } | |||||
inline void clear() noexcept { data[0] = 0; data[1] = 0; data[2] = 0; } | |||||
inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} | |||||
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } | |||||
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } | |||||
inline void copyFromSameType (Int24& source) noexcept { data[0] = source.data[0]; data[1] = source.data[1]; data[2] = source.data[2]; } | |||||
char* data; | |||||
enum { bytesPerSample = 3, maxValue = 0x7fffff, resolution = (1 << 8), isFloat = 0 }; | |||||
}; | |||||
class Int32 | |||||
{ | |||||
public: | |||||
inline Int32 (void* d) noexcept : data (static_cast <uint32*> (d)) {} | |||||
inline void advance() noexcept { ++data; } | |||||
inline void skip (int numSamples) noexcept { data += numSamples; } | |||||
inline float getAsFloatLE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int32) ByteOrder::swapIfBigEndian (*data)); } | |||||
inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int32) ByteOrder::swapIfLittleEndian (*data)); } | |||||
inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) (maxValue * jlimit (-1.0, 1.0, (double) newValue))); } | |||||
inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) (maxValue * jlimit (-1.0, 1.0, (double) newValue))); } | |||||
inline int32 getAsInt32LE() const noexcept { return (int32) ByteOrder::swapIfBigEndian (*data); } | |||||
inline int32 getAsInt32BE() const noexcept { return (int32) ByteOrder::swapIfLittleEndian (*data); } | |||||
inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) newValue); } | |||||
inline void setAsInt32BE (int32 newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) newValue); } | |||||
inline void clear() noexcept { *data = 0; } | |||||
inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} | |||||
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } | |||||
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } | |||||
inline void copyFromSameType (Int32& source) noexcept { *data = *source.data; } | |||||
uint32* data; | |||||
enum { bytesPerSample = 4, maxValue = 0x7fffffff, resolution = 1, isFloat = 0 }; | |||||
}; | |||||
class Float32 | |||||
{ | |||||
public: | |||||
inline Float32 (void* d) noexcept : data (static_cast <float*> (d)) {} | |||||
inline void advance() noexcept { ++data; } | |||||
inline void skip (int numSamples) noexcept { data += numSamples; } | |||||
#if JUCE_BIG_ENDIAN | |||||
inline float getAsFloatBE() const noexcept { return *data; } | |||||
inline void setAsFloatBE (float newValue) noexcept { *data = newValue; } | |||||
inline float getAsFloatLE() const noexcept { union { uint32 asInt; float asFloat; } n; n.asInt = ByteOrder::swap (*(uint32*) data); return n.asFloat; } | |||||
inline void setAsFloatLE (float newValue) noexcept { union { uint32 asInt; float asFloat; } n; n.asFloat = newValue; *(uint32*) data = ByteOrder::swap (n.asInt); } | |||||
#else | |||||
inline float getAsFloatLE() const noexcept { return *data; } | |||||
inline void setAsFloatLE (float newValue) noexcept { *data = newValue; } | |||||
inline float getAsFloatBE() const noexcept { union { uint32 asInt; float asFloat; } n; n.asInt = ByteOrder::swap (*(uint32*) data); return n.asFloat; } | |||||
inline void setAsFloatBE (float newValue) noexcept { union { uint32 asInt; float asFloat; } n; n.asFloat = newValue; *(uint32*) data = ByteOrder::swap (n.asInt); } | |||||
#endif | |||||
inline int32 getAsInt32LE() const noexcept { return (int32) roundToInt (jlimit (-1.0, 1.0, (double) getAsFloatLE()) * (double) maxValue); } | |||||
inline int32 getAsInt32BE() const noexcept { return (int32) roundToInt (jlimit (-1.0, 1.0, (double) getAsFloatBE()) * (double) maxValue); } | |||||
inline void setAsInt32LE (int32 newValue) noexcept { setAsFloatLE ((float) (newValue * (1.0 / (1.0 + maxValue)))); } | |||||
inline void setAsInt32BE (int32 newValue) noexcept { setAsFloatBE ((float) (newValue * (1.0 / (1.0 + maxValue)))); } | |||||
inline void clear() noexcept { *data = 0; } | |||||
inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} | |||||
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsFloatLE (source.getAsFloat()); } | |||||
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsFloatBE (source.getAsFloat()); } | |||||
inline void copyFromSameType (Float32& source) noexcept { *data = *source.data; } | |||||
float* data; | |||||
enum { bytesPerSample = 4, maxValue = 0x7fffffff, resolution = (1 << 8), isFloat = 1 }; | |||||
}; | |||||
//============================================================================== | |||||
class NonInterleaved | |||||
{ | |||||
public: | |||||
inline NonInterleaved() noexcept {} | |||||
inline NonInterleaved (const NonInterleaved&) noexcept {} | |||||
inline NonInterleaved (const int) noexcept {} | |||||
inline void copyFrom (const NonInterleaved&) noexcept {} | |||||
template <class SampleFormatType> inline void advanceData (SampleFormatType& s) noexcept { s.advance(); } | |||||
template <class SampleFormatType> inline void advanceDataBy (SampleFormatType& s, int numSamples) noexcept { s.skip (numSamples); } | |||||
template <class SampleFormatType> inline void clear (SampleFormatType& s, int numSamples) noexcept { s.clearMultiple (numSamples); } | |||||
template <class SampleFormatType> inline static int getNumBytesBetweenSamples (const SampleFormatType&) noexcept { return SampleFormatType::bytesPerSample; } | |||||
enum { isInterleavedType = 0, numInterleavedChannels = 1 }; | |||||
}; | |||||
class Interleaved | |||||
{ | |||||
public: | |||||
inline Interleaved() noexcept : numInterleavedChannels (1) {} | |||||
inline Interleaved (const Interleaved& other) noexcept : numInterleavedChannels (other.numInterleavedChannels) {} | |||||
inline Interleaved (const int numInterleavedChans) noexcept : numInterleavedChannels (numInterleavedChans) {} | |||||
inline void copyFrom (const Interleaved& other) noexcept { numInterleavedChannels = other.numInterleavedChannels; } | |||||
template <class SampleFormatType> inline void advanceData (SampleFormatType& s) noexcept { s.skip (numInterleavedChannels); } | |||||
template <class SampleFormatType> inline void advanceDataBy (SampleFormatType& s, int numSamples) noexcept { s.skip (numInterleavedChannels * numSamples); } | |||||
template <class SampleFormatType> inline void clear (SampleFormatType& s, int numSamples) noexcept { while (--numSamples >= 0) { s.clear(); s.skip (numInterleavedChannels); } } | |||||
template <class SampleFormatType> inline int getNumBytesBetweenSamples (const SampleFormatType&) const noexcept { return numInterleavedChannels * SampleFormatType::bytesPerSample; } | |||||
int numInterleavedChannels; | |||||
enum { isInterleavedType = 1 }; | |||||
}; | |||||
//============================================================================== | |||||
class NonConst | |||||
{ | |||||
public: | |||||
typedef void VoidType; | |||||
static inline void* toVoidPtr (VoidType* v) noexcept { return v; } | |||||
enum { isConst = 0 }; | |||||
}; | |||||
class Const | |||||
{ | |||||
public: | |||||
typedef const void VoidType; | |||||
static inline void* toVoidPtr (VoidType* v) noexcept { return const_cast <void*> (v); } | |||||
enum { isConst = 1 }; | |||||
}; | |||||
#endif | |||||
//============================================================================== | |||||
/** | |||||
A pointer to a block of audio data with a particular encoding. | |||||
This object can be used to read and write from blocks of encoded audio samples. To create one, you specify | |||||
the audio format as a series of template parameters, e.g. | |||||
@code | |||||
// this creates a pointer for reading from a const array of 16-bit little-endian packed samples. | |||||
AudioData::Pointer <AudioData::Int16, | |||||
AudioData::LittleEndian, | |||||
AudioData::NonInterleaved, | |||||
AudioData::Const> pointer (someRawAudioData); | |||||
// These methods read the sample that is being pointed to | |||||
float firstSampleAsFloat = pointer.getAsFloat(); | |||||
int32 firstSampleAsInt = pointer.getAsInt32(); | |||||
++pointer; // moves the pointer to the next sample. | |||||
pointer += 3; // skips the next 3 samples. | |||||
@endcode | |||||
The convertSamples() method lets you copy a range of samples from one format to another, automatically | |||||
converting its format. | |||||
@see AudioData::Converter | |||||
*/ | |||||
template <typename SampleFormat, | |||||
typename Endianness, | |||||
typename InterleavingType, | |||||
typename Constness> | |||||
class Pointer : private InterleavingType // (inherited for EBCO) | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a non-interleaved pointer from some raw data in the appropriate format. | |||||
This constructor is only used if you've specified the AudioData::NonInterleaved option - | |||||
for interleaved formats, use the constructor that also takes a number of channels. | |||||
*/ | |||||
Pointer (typename Constness::VoidType* sourceData) noexcept | |||||
: data (Constness::toVoidPtr (sourceData)) | |||||
{ | |||||
// If you're using interleaved data, call the other constructor! If you're using non-interleaved data, | |||||
// you should pass NonInterleaved as the template parameter for the interleaving type! | |||||
static_jassert (InterleavingType::isInterleavedType == 0); | |||||
} | |||||
/** Creates a pointer from some raw data in the appropriate format with the specified number of interleaved channels. | |||||
For non-interleaved data, use the other constructor. | |||||
*/ | |||||
Pointer (typename Constness::VoidType* sourceData, int numInterleavedChannels) noexcept | |||||
: InterleavingType (numInterleavedChannels), 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! | |||||
Pointer dest (*this); | |||||
while (--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); | |||||
} | |||||
/** Returns true if the pointer is using a floating-point format. */ | |||||
static bool isFloatingPoint() noexcept { return (bool) SampleFormat::isFloat; } | |||||
/** Returns true if the format is big-endian. */ | |||||
static bool isBigEndian() noexcept { return (bool) Endianness::isBigEndian; } | |||||
/** Returns the number of bytes in each sample (ignoring the number of interleaved channels). */ | |||||
static int getBytesPerSample() noexcept { return (int) SampleFormat::bytesPerSample; } | |||||
/** Returns the number of interleaved channels in the format. */ | |||||
int getNumInterleavedChannels() const noexcept { return (int) this->numInterleavedChannels; } | |||||
/** Returns the number of bytes between the start address of each sample. */ | |||||
int getNumBytesBetweenSamples() const noexcept { return InterleavingType::getNumBytesBetweenSamples (data); } | |||||
/** Returns the accuracy of this format when represented as a 32-bit integer. | |||||
This is the smallest number above 0 that can be represented in the sample format, converted to | |||||
a 32-bit range. E,g. if the format is 8-bit, its resolution is 0x01000000; if the format is 24-bit, | |||||
its resolution is 0x100. | |||||
*/ | |||||
static int get32BitResolution() noexcept { return (int) SampleFormat::resolution; } | |||||
/** Returns a pointer to the underlying data. */ | |||||
const void* getRawData() const noexcept { return data.data; } | |||||
private: | |||||
//============================================================================== | |||||
SampleFormat data; | |||||
inline void advance() noexcept { this->advanceData (data); } | |||||
Pointer operator++ (int); // private to force you to use the more efficient pre-increment! | |||||
Pointer operator-- (int); | |||||
}; | |||||
//============================================================================== | |||||
/** A base class for objects that are used to convert between two different sample formats. | |||||
The AudioData::ConverterInstance implements this base class and can be templated, so | |||||
you can create an instance that converts between two particular formats, and then | |||||
store this in the abstract base class. | |||||
@see AudioData::ConverterInstance | |||||
*/ | |||||
class Converter | |||||
{ | |||||
public: | |||||
virtual ~Converter() {} | |||||
/** Converts a sequence of samples from the converter's source format into the dest format. */ | |||||
virtual void convertSamples (void* destSamples, const void* sourceSamples, int numSamples) const = 0; | |||||
/** Converts a sequence of samples from the converter's source format into the dest format. | |||||
This method takes sub-channel indexes, which can be used with interleaved formats in order to choose a | |||||
particular sub-channel of the data to be used. | |||||
*/ | |||||
virtual void convertSamples (void* destSamples, int destSubChannel, | |||||
const void* sourceSamples, int sourceSubChannel, int numSamples) const = 0; | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
A class that converts between two templated AudioData::Pointer types, and which | |||||
implements the AudioData::Converter interface. | |||||
This can be used as a concrete instance of the AudioData::Converter abstract class. | |||||
@see AudioData::Converter | |||||
*/ | |||||
template <class SourceSampleType, class DestSampleType> | |||||
class ConverterInstance : public Converter | |||||
{ | |||||
public: | |||||
ConverterInstance (int numSourceChannels = 1, int numDestChannels = 1) | |||||
: sourceChannels (numSourceChannels), destChannels (numDestChannels) | |||||
{} | |||||
~ConverterInstance() {} | |||||
void convertSamples (void* dest, const void* source, int numSamples) const | |||||
{ | |||||
SourceSampleType s (source, sourceChannels); | |||||
DestSampleType d (dest, destChannels); | |||||
d.convertSamples (s, numSamples); | |||||
} | |||||
void convertSamples (void* dest, int destSubChannel, | |||||
const void* source, int sourceSubChannel, int numSamples) const | |||||
{ | |||||
jassert (destSubChannel < destChannels && sourceSubChannel < sourceChannels); | |||||
SourceSampleType s (addBytesToPointer (source, sourceSubChannel * SourceSampleType::getBytesPerSample()), sourceChannels); | |||||
DestSampleType d (addBytesToPointer (dest, destSubChannel * DestSampleType::getBytesPerSample()), destChannels); | |||||
d.convertSamples (s, numSamples); | |||||
} | |||||
private: | |||||
JUCE_DECLARE_NON_COPYABLE (ConverterInstance) | |||||
const int sourceChannels, destChannels; | |||||
}; | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
A set of routines to convert buffers of 32-bit floating point data to and from | |||||
various integer formats. | |||||
Note that these functions are deprecated - the AudioData class provides a much more | |||||
flexible set of conversion classes now. | |||||
*/ | |||||
class JUCE_API AudioDataConverters | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
static void convertFloatToInt16LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 2); | |||||
static void convertFloatToInt16BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 2); | |||||
static void convertFloatToInt24LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 3); | |||||
static void convertFloatToInt24BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 3); | |||||
static void convertFloatToInt32LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); | |||||
static void convertFloatToInt32BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); | |||||
static void convertFloatToFloat32LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); | |||||
static void convertFloatToFloat32BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); | |||||
//============================================================================== | |||||
static void convertInt16LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 2); | |||||
static void convertInt16BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 2); | |||||
static void convertInt24LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 3); | |||||
static void convertInt24BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 3); | |||||
static void convertInt32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); | |||||
static void convertInt32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); | |||||
static void convertFloat32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); | |||||
static void convertFloat32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); | |||||
//============================================================================== | |||||
enum DataFormat | |||||
{ | |||||
int16LE, | |||||
int16BE, | |||||
int24LE, | |||||
int24BE, | |||||
int32LE, | |||||
int32BE, | |||||
float32LE, | |||||
float32BE, | |||||
}; | |||||
static void convertFloatToFormat (DataFormat destFormat, | |||||
const float* source, void* dest, int numSamples); | |||||
static void convertFormatToFloat (DataFormat sourceFormat, | |||||
const void* source, float* dest, int numSamples); | |||||
//============================================================================== | |||||
static void interleaveSamples (const float** source, float* dest, | |||||
int numSamples, int numChannels); | |||||
static void deinterleaveSamples (const float* source, float** dest, | |||||
int numSamples, int numChannels); | |||||
private: | |||||
AudioDataConverters(); | |||||
JUCE_DECLARE_NON_COPYABLE (AudioDataConverters) | |||||
}; | |||||
#endif // __JUCE_AUDIODATACONVERTERS_JUCEHEADER__ |
@@ -0,0 +1,560 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
AudioSampleBuffer::AudioSampleBuffer (const int numChannels_, | |||||
const int numSamples) noexcept | |||||
: numChannels (numChannels_), | |||||
size (numSamples) | |||||
{ | |||||
jassert (numSamples >= 0); | |||||
jassert (numChannels_ > 0); | |||||
allocateData(); | |||||
} | |||||
AudioSampleBuffer::AudioSampleBuffer (const AudioSampleBuffer& other) noexcept | |||||
: numChannels (other.numChannels), | |||||
size (other.size) | |||||
{ | |||||
allocateData(); | |||||
const size_t numBytes = sizeof (float) * (size_t) size; | |||||
for (int i = 0; i < numChannels; ++i) | |||||
memcpy (channels[i], other.channels[i], numBytes); | |||||
} | |||||
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] = 0; | |||||
} | |||||
AudioSampleBuffer::AudioSampleBuffer (float* const* dataToReferTo, | |||||
const int numChannels_, | |||||
const int numSamples) noexcept | |||||
: numChannels (numChannels_), | |||||
size (numSamples), | |||||
allocatedBytes (0) | |||||
{ | |||||
jassert (numChannels_ > 0); | |||||
allocateChannels (dataToReferTo, 0); | |||||
} | |||||
AudioSampleBuffer::AudioSampleBuffer (float* const* dataToReferTo, | |||||
const int numChannels_, | |||||
const int startSample, | |||||
const int numSamples) noexcept | |||||
: numChannels (numChannels_), | |||||
size (numSamples), | |||||
allocatedBytes (0) | |||||
{ | |||||
jassert (numChannels_ > 0); | |||||
allocateChannels (dataToReferTo, startSample); | |||||
} | |||||
void AudioSampleBuffer::setDataToReferTo (float** dataToReferTo, | |||||
const int newNumChannels, | |||||
const int newNumSamples) noexcept | |||||
{ | |||||
jassert (newNumChannels > 0); | |||||
allocatedBytes = 0; | |||||
allocatedData.free(); | |||||
numChannels = newNumChannels; | |||||
size = newNumSamples; | |||||
allocateChannels (dataToReferTo, 0); | |||||
} | |||||
void AudioSampleBuffer::allocateChannels (float* const* const dataToReferTo, int offset) | |||||
{ | |||||
// (try to avoid doing a malloc here, as that'll blow up things like Pro-Tools) | |||||
if (numChannels < (int) numElementsInArray (preallocatedChannelSpace)) | |||||
{ | |||||
channels = static_cast <float**> (preallocatedChannelSpace); | |||||
} | |||||
else | |||||
{ | |||||
allocatedData.malloc ((size_t) numChannels + 1, sizeof (float*)); | |||||
channels = reinterpret_cast <float**> (allocatedData.getData()); | |||||
} | |||||
for (int i = 0; i < numChannels; ++i) | |||||
{ | |||||
// you have to pass in the same number of valid pointers as numChannels | |||||
jassert (dataToReferTo[i] != nullptr); | |||||
channels[i] = dataToReferTo[i] + offset; | |||||
} | |||||
channels [numChannels] = 0; | |||||
} | |||||
AudioSampleBuffer& AudioSampleBuffer::operator= (const AudioSampleBuffer& other) noexcept | |||||
{ | |||||
if (this != &other) | |||||
{ | |||||
setSize (other.getNumChannels(), other.getNumSamples(), false, false, false); | |||||
const size_t numBytes = sizeof (float) * (size_t) size; | |||||
for (int i = 0; i < numChannels; ++i) | |||||
memcpy (channels[i], other.channels[i], numBytes); | |||||
} | |||||
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 channelListSize = sizeof (float*) * (size_t) (newNumChannels + 1); | |||||
const size_t newTotalBytes = ((size_t) newNumChannels * (size_t) newNumSamples * sizeof (float)) + channelListSize + 32; | |||||
if (keepExistingContent) | |||||
{ | |||||
HeapBlock <char, true> newData; | |||||
newData.allocate (newTotalBytes, clearExtraSpace); | |||||
const size_t numBytesToCopy = sizeof (float) * (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 += newNumSamples; | |||||
} | |||||
const int numChansToCopy = jmin (numChannels, newNumChannels); | |||||
for (int i = 0; i < numChansToCopy; ++i) | |||||
memcpy (newChannels[i], channels[i], numBytesToCopy); | |||||
allocatedData.swapWith (newData); | |||||
allocatedBytes = newTotalBytes; | |||||
channels = newChannels; | |||||
} | |||||
else | |||||
{ | |||||
if (avoidReallocating && allocatedBytes >= newTotalBytes) | |||||
{ | |||||
if (clearExtraSpace) | |||||
allocatedData.clear (newTotalBytes); | |||||
} | |||||
else | |||||
{ | |||||
allocatedBytes = newTotalBytes; | |||||
allocatedData.allocate (newTotalBytes, clearExtraSpace); | |||||
channels = reinterpret_cast <float**> (allocatedData.getData()); | |||||
} | |||||
float* chan = reinterpret_cast <float*> (allocatedData + channelListSize); | |||||
for (int i = 0; i < newNumChannels; ++i) | |||||
{ | |||||
channels[i] = chan; | |||||
chan += newNumSamples; | |||||
} | |||||
} | |||||
channels [newNumChannels] = 0; | |||||
size = newNumSamples; | |||||
numChannels = newNumChannels; | |||||
} | |||||
} | |||||
void AudioSampleBuffer::clear() noexcept | |||||
{ | |||||
for (int i = 0; i < numChannels; ++i) | |||||
zeromem (channels[i], sizeof (float) * (size_t) size); | |||||
} | |||||
void AudioSampleBuffer::clear (const int startSample, | |||||
const int numSamples) noexcept | |||||
{ | |||||
jassert (startSample >= 0 && startSample + numSamples <= size); | |||||
for (int i = 0; i < numChannels; ++i) | |||||
zeromem (channels [i] + startSample, sizeof (float) * (size_t) numSamples); | |||||
} | |||||
void AudioSampleBuffer::clear (const int channel, | |||||
const int startSample, | |||||
const int numSamples) noexcept | |||||
{ | |||||
jassert (isPositiveAndBelow (channel, numChannels)); | |||||
jassert (startSample >= 0 && startSample + numSamples <= size); | |||||
zeromem (channels [channel] + startSample, sizeof (float) * (size_t) numSamples); | |||||
} | |||||
void AudioSampleBuffer::applyGain (const int channel, | |||||
const int startSample, | |||||
int numSamples, | |||||
const float gain) noexcept | |||||
{ | |||||
jassert (isPositiveAndBelow (channel, numChannels)); | |||||
jassert (startSample >= 0 && startSample + numSamples <= size); | |||||
if (gain != 1.0f) | |||||
{ | |||||
float* d = channels [channel] + startSample; | |||||
if (gain == 0.0f) | |||||
{ | |||||
zeromem (d, sizeof (float) * (size_t) numSamples); | |||||
} | |||||
else | |||||
{ | |||||
while (--numSamples >= 0) | |||||
*d++ *= gain; | |||||
} | |||||
} | |||||
} | |||||
void AudioSampleBuffer::applyGainRamp (const int channel, | |||||
const int startSample, | |||||
int numSamples, | |||||
float startGain, | |||||
float endGain) noexcept | |||||
{ | |||||
if (startGain == endGain) | |||||
{ | |||||
applyGain (channel, startSample, numSamples, startGain); | |||||
} | |||||
else | |||||
{ | |||||
jassert (isPositiveAndBelow (channel, numChannels)); | |||||
jassert (startSample >= 0 && startSample + numSamples <= size); | |||||
const float increment = (endGain - startGain) / numSamples; | |||||
float* d = channels [channel] + startSample; | |||||
while (--numSamples >= 0) | |||||
{ | |||||
*d++ *= startGain; | |||||
startGain += increment; | |||||
} | |||||
} | |||||
} | |||||
void AudioSampleBuffer::applyGain (const int startSample, | |||||
const int numSamples, | |||||
const float gain) noexcept | |||||
{ | |||||
for (int i = 0; i < numChannels; ++i) | |||||
applyGain (i, startSample, numSamples, gain); | |||||
} | |||||
void AudioSampleBuffer::applyGainRamp (const int startSample, | |||||
const int numSamples, | |||||
const float startGain, | |||||
const float endGain) noexcept | |||||
{ | |||||
for (int i = 0; i < numChannels; ++i) | |||||
applyGainRamp (i, startSample, numSamples, startGain, endGain); | |||||
} | |||||
void AudioSampleBuffer::addFrom (const int destChannel, | |||||
const int destStartSample, | |||||
const AudioSampleBuffer& source, | |||||
const int sourceChannel, | |||||
const int sourceStartSample, | |||||
int numSamples, | |||||
const float gain) noexcept | |||||
{ | |||||
jassert (&source != this || sourceChannel != destChannel); | |||||
jassert (isPositiveAndBelow (destChannel, numChannels)); | |||||
jassert (destStartSample >= 0 && destStartSample + numSamples <= size); | |||||
jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); | |||||
jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); | |||||
if (gain != 0.0f && numSamples > 0) | |||||
{ | |||||
float* d = channels [destChannel] + destStartSample; | |||||
const float* s = source.channels [sourceChannel] + sourceStartSample; | |||||
if (gain != 1.0f) | |||||
{ | |||||
while (--numSamples >= 0) | |||||
*d++ += gain * *s++; | |||||
} | |||||
else | |||||
{ | |||||
while (--numSamples >= 0) | |||||
*d++ += *s++; | |||||
} | |||||
} | |||||
} | |||||
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* d = channels [destChannel] + destStartSample; | |||||
if (gain != 1.0f) | |||||
{ | |||||
while (--numSamples >= 0) | |||||
*d++ += gain * *source++; | |||||
} | |||||
else | |||||
{ | |||||
while (--numSamples >= 0) | |||||
*d++ += *source++; | |||||
} | |||||
} | |||||
} | |||||
void AudioSampleBuffer::addFromWithRamp (const int destChannel, | |||||
const int destStartSample, | |||||
const float* source, | |||||
int numSamples, | |||||
float startGain, | |||||
const float endGain) noexcept | |||||
{ | |||||
jassert (isPositiveAndBelow (destChannel, numChannels)); | |||||
jassert (destStartSample >= 0 && destStartSample + numSamples <= size); | |||||
jassert (source != nullptr); | |||||
if (startGain == endGain) | |||||
{ | |||||
addFrom (destChannel, | |||||
destStartSample, | |||||
source, | |||||
numSamples, | |||||
startGain); | |||||
} | |||||
else | |||||
{ | |||||
if (numSamples > 0 && (startGain != 0.0f || endGain != 0.0f)) | |||||
{ | |||||
const float increment = (endGain - startGain) / numSamples; | |||||
float* d = channels [destChannel] + destStartSample; | |||||
while (--numSamples >= 0) | |||||
{ | |||||
*d++ += startGain * *source++; | |||||
startGain += increment; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
void AudioSampleBuffer::copyFrom (const int destChannel, | |||||
const int destStartSample, | |||||
const AudioSampleBuffer& source, | |||||
const int sourceChannel, | |||||
const int sourceStartSample, | |||||
int numSamples) noexcept | |||||
{ | |||||
jassert (&source != this || sourceChannel != destChannel); | |||||
jassert (isPositiveAndBelow (destChannel, numChannels)); | |||||
jassert (destStartSample >= 0 && destStartSample + numSamples <= size); | |||||
jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); | |||||
jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); | |||||
if (numSamples > 0) | |||||
{ | |||||
memcpy (channels [destChannel] + destStartSample, | |||||
source.channels [sourceChannel] + sourceStartSample, | |||||
sizeof (float) * (size_t) 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) | |||||
{ | |||||
memcpy (channels [destChannel] + destStartSample, | |||||
source, | |||||
sizeof (float) * (size_t) numSamples); | |||||
} | |||||
} | |||||
void AudioSampleBuffer::copyFrom (const int destChannel, | |||||
const int destStartSample, | |||||
const float* source, | |||||
int numSamples, | |||||
const float gain) noexcept | |||||
{ | |||||
jassert (isPositiveAndBelow (destChannel, numChannels)); | |||||
jassert (destStartSample >= 0 && destStartSample + numSamples <= size); | |||||
jassert (source != nullptr); | |||||
if (numSamples > 0) | |||||
{ | |||||
float* d = channels [destChannel] + destStartSample; | |||||
if (gain != 1.0f) | |||||
{ | |||||
if (gain == 0) | |||||
{ | |||||
zeromem (d, sizeof (float) * (size_t) numSamples); | |||||
} | |||||
else | |||||
{ | |||||
while (--numSamples >= 0) | |||||
*d++ = gain * *source++; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
memcpy (d, source, sizeof (float) * (size_t) numSamples); | |||||
} | |||||
} | |||||
} | |||||
void AudioSampleBuffer::copyFromWithRamp (const int destChannel, | |||||
const int destStartSample, | |||||
const float* source, | |||||
int numSamples, | |||||
float startGain, | |||||
float endGain) noexcept | |||||
{ | |||||
jassert (isPositiveAndBelow (destChannel, numChannels)); | |||||
jassert (destStartSample >= 0 && destStartSample + numSamples <= size); | |||||
jassert (source != nullptr); | |||||
if (startGain == endGain) | |||||
{ | |||||
copyFrom (destChannel, | |||||
destStartSample, | |||||
source, | |||||
numSamples, | |||||
startGain); | |||||
} | |||||
else | |||||
{ | |||||
if (numSamples > 0 && (startGain != 0.0f || endGain != 0.0f)) | |||||
{ | |||||
const float increment = (endGain - startGain) / numSamples; | |||||
float* d = channels [destChannel] + destStartSample; | |||||
while (--numSamples >= 0) | |||||
{ | |||||
*d++ = startGain * *source++; | |||||
startGain += increment; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
void AudioSampleBuffer::findMinMax (const int channel, | |||||
const int startSample, | |||||
int numSamples, | |||||
float& minVal, | |||||
float& maxVal) const noexcept | |||||
{ | |||||
jassert (isPositiveAndBelow (channel, numChannels)); | |||||
jassert (startSample >= 0 && startSample + numSamples <= size); | |||||
findMinAndMax (channels [channel] + startSample, numSamples, minVal, maxVal); | |||||
} | |||||
float AudioSampleBuffer::getMagnitude (const int channel, | |||||
const int startSample, | |||||
const int numSamples) const noexcept | |||||
{ | |||||
jassert (isPositiveAndBelow (channel, numChannels)); | |||||
jassert (startSample >= 0 && startSample + numSamples <= size); | |||||
float mn, mx; | |||||
findMinMax (channel, startSample, numSamples, mn, mx); | |||||
return jmax (mn, -mn, mx, -mx); | |||||
} | |||||
float AudioSampleBuffer::getMagnitude (const int startSample, | |||||
const int numSamples) const noexcept | |||||
{ | |||||
float mag = 0.0f; | |||||
for (int i = 0; i < numChannels; ++i) | |||||
mag = jmax (mag, getMagnitude (i, startSample, numSamples)); | |||||
return mag; | |||||
} | |||||
float AudioSampleBuffer::getRMSLevel (const int channel, | |||||
const int startSample, | |||||
const int numSamples) const noexcept | |||||
{ | |||||
jassert (isPositiveAndBelow (channel, numChannels)); | |||||
jassert (startSample >= 0 && startSample + numSamples <= size); | |||||
if (numSamples <= 0 || channel < 0 || channel >= numChannels) | |||||
return 0.0f; | |||||
const float* const data = channels [channel] + startSample; | |||||
double sum = 0.0; | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
const float sample = data [i]; | |||||
sum += sample * sample; | |||||
} | |||||
return (float) std::sqrt (sum / numSamples); | |||||
} |
@@ -0,0 +1,444 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_AUDIOSAMPLEBUFFER_JUCEHEADER__ | |||||
#define __JUCE_AUDIOSAMPLEBUFFER_JUCEHEADER__ | |||||
//============================================================================== | |||||
/** | |||||
A multi-channel buffer of 32-bit floating point audio samples. | |||||
*/ | |||||
class JUCE_API AudioSampleBuffer | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a buffer with a specified number of channels and samples. | |||||
The contents of the buffer will initially be undefined, so use clear() to | |||||
set all the samples to zero. | |||||
The buffer will allocate its memory internally, and this will be released | |||||
when the buffer is deleted. If the memory can't be allocated, this will | |||||
throw a std::bad_alloc exception. | |||||
*/ | |||||
AudioSampleBuffer (int numChannels, | |||||
int numSamples) noexcept; | |||||
/** Creates a buffer using a pre-allocated block of memory. | |||||
Note that if the buffer is resized or its number of channels is changed, it | |||||
will re-allocate memory internally and copy the existing data to this new area, | |||||
so it will then stop directly addressing this memory. | |||||
@param dataToReferTo a pre-allocated array containing pointers to the data | |||||
for each channel that should be used by this buffer. The | |||||
buffer will only refer to this memory, it won't try to delete | |||||
it when the buffer is deleted or resized. | |||||
@param numChannels the number of channels to use - this must correspond to the | |||||
number of elements in the array passed in | |||||
@param numSamples the number of samples to use - this must correspond to the | |||||
size of the arrays passed in | |||||
*/ | |||||
AudioSampleBuffer (float* const* dataToReferTo, | |||||
int numChannels, | |||||
int numSamples) noexcept; | |||||
/** Creates a buffer using a pre-allocated block of memory. | |||||
Note that if the buffer is resized or its number of channels is changed, it | |||||
will re-allocate memory internally and copy the existing data to this new area, | |||||
so it will then stop directly addressing this memory. | |||||
@param dataToReferTo a pre-allocated array containing pointers to the data | |||||
for each channel that should be used by this buffer. The | |||||
buffer will only refer to this memory, it won't try to delete | |||||
it when the buffer is deleted or resized. | |||||
@param numChannels the number of channels to use - this must correspond to the | |||||
number of elements in the array passed in | |||||
@param startSample the offset within the arrays at which the data begins | |||||
@param numSamples the number of samples to use - this must correspond to the | |||||
size of the arrays passed in | |||||
*/ | |||||
AudioSampleBuffer (float* const* dataToReferTo, | |||||
int numChannels, | |||||
int startSample, | |||||
int numSamples) noexcept; | |||||
/** Copies another buffer. | |||||
This buffer will make its own copy of the other's data, unless the buffer was created | |||||
using an external data buffer, in which case boths buffers will just point to the same | |||||
shared block of data. | |||||
*/ | |||||
AudioSampleBuffer (const AudioSampleBuffer& other) noexcept; | |||||
/** Copies another buffer onto this one. | |||||
This buffer's size will be changed to that of the other buffer. | |||||
*/ | |||||
AudioSampleBuffer& operator= (const AudioSampleBuffer& other) noexcept; | |||||
/** Destructor. | |||||
This will free any memory allocated by the buffer. | |||||
*/ | |||||
virtual ~AudioSampleBuffer() noexcept; | |||||
//============================================================================== | |||||
/** Returns the number of channels of audio data that this buffer contains. | |||||
@see getSampleData | |||||
*/ | |||||
int getNumChannels() const noexcept { return numChannels; } | |||||
/** Returns the number of samples allocated in each of the buffer's channels. | |||||
@see getSampleData | |||||
*/ | |||||
int getNumSamples() const noexcept { return size; } | |||||
/** Returns a pointer one of the buffer's channels. | |||||
For speed, this doesn't check whether the channel number is out of range, | |||||
so be careful when using it! | |||||
*/ | |||||
float* getSampleData (const int channelNumber) const noexcept | |||||
{ | |||||
jassert (isPositiveAndBelow (channelNumber, numChannels)); | |||||
return channels [channelNumber]; | |||||
} | |||||
/** Returns a pointer to a sample in one of the buffer's channels. | |||||
For speed, this doesn't check whether the channel and sample number | |||||
are out-of-range, so be careful when using it! | |||||
*/ | |||||
float* getSampleData (const int channelNumber, | |||||
const int sampleOffset) const noexcept | |||||
{ | |||||
jassert (isPositiveAndBelow (channelNumber, numChannels)); | |||||
jassert (isPositiveAndBelow (sampleOffset, size)); | |||||
return channels [channelNumber] + sampleOffset; | |||||
} | |||||
/** Returns an array of pointers to the channels in the buffer. | |||||
Don't modify any of the pointers that are returned, and bear in mind that | |||||
these will become invalid if the buffer is resized. | |||||
*/ | |||||
float** getArrayOfChannels() const noexcept { return channels; } | |||||
//============================================================================== | |||||
/** Changes the buffer's size or number of channels. | |||||
This can expand or contract the buffer's length, and add or remove channels. | |||||
If keepExistingContent is true, it will try to preserve as much of the | |||||
old data as it can in the new buffer. | |||||
If clearExtraSpace is true, then any extra channels or space that is | |||||
allocated will be also be cleared. If false, then this space is left | |||||
uninitialised. | |||||
If avoidReallocating is true, then changing the buffer's size won't reduce the | |||||
amount of memory that is currently allocated (but it will still increase it if | |||||
the new size is bigger than the amount it currently has). If this is false, then | |||||
a new allocation will be done so that the buffer uses takes up the minimum amount | |||||
of memory that it needs. | |||||
If the required memory can't be allocated, this will throw a std::bad_alloc exception. | |||||
*/ | |||||
void setSize (int newNumChannels, | |||||
int newNumSamples, | |||||
bool keepExistingContent = false, | |||||
bool clearExtraSpace = false, | |||||
bool avoidReallocating = false) noexcept; | |||||
/** Makes this buffer point to a pre-allocated set of channel data arrays. | |||||
There's also a constructor that lets you specify arrays like this, but this | |||||
lets you change the channels dynamically. | |||||
Note that if the buffer is resized or its number of channels is changed, it | |||||
will re-allocate memory internally and copy the existing data to this new area, | |||||
so it will then stop directly addressing this memory. | |||||
@param dataToReferTo a pre-allocated array containing pointers to the data | |||||
for each channel that should be used by this buffer. The | |||||
buffer will only refer to this memory, it won't try to delete | |||||
it when the buffer is deleted or resized. | |||||
@param numChannels the number of channels to use - this must correspond to the | |||||
number of elements in the array passed in | |||||
@param numSamples the number of samples to use - this must correspond to the | |||||
size of the arrays passed in | |||||
*/ | |||||
void setDataToReferTo (float** dataToReferTo, | |||||
int numChannels, | |||||
int numSamples) noexcept; | |||||
//============================================================================== | |||||
/** Clears all the samples in all channels. */ | |||||
void clear() noexcept; | |||||
/** Clears a specified region of all the channels. | |||||
For speed, this doesn't check whether the channel and sample number | |||||
are in-range, so be careful! | |||||
*/ | |||||
void clear (int startSample, | |||||
int numSamples) noexcept; | |||||
/** Clears a specified region of just one channel. | |||||
For speed, this doesn't check whether the channel and sample number | |||||
are in-range, so be careful! | |||||
*/ | |||||
void clear (int channel, | |||||
int startSample, | |||||
int numSamples) noexcept; | |||||
/** Applies a gain multiple to a region of one channel. | |||||
For speed, this doesn't check whether the channel and sample number | |||||
are in-range, so be careful! | |||||
*/ | |||||
void applyGain (int channel, | |||||
int startSample, | |||||
int numSamples, | |||||
float gain) noexcept; | |||||
/** Applies a gain multiple to a region of all the channels. | |||||
For speed, this doesn't check whether the sample numbers | |||||
are in-range, so be careful! | |||||
*/ | |||||
void applyGain (int startSample, | |||||
int numSamples, | |||||
float gain) noexcept; | |||||
/** Applies a range of gains to a region of a channel. | |||||
The gain that is applied to each sample will vary from | |||||
startGain on the first sample to endGain on the last Sample, | |||||
so it can be used to do basic fades. | |||||
For speed, this doesn't check whether the sample numbers | |||||
are in-range, so be careful! | |||||
*/ | |||||
void applyGainRamp (int channel, | |||||
int startSample, | |||||
int numSamples, | |||||
float startGain, | |||||
float endGain) noexcept; | |||||
/** Applies a range of gains to a region of all channels. | |||||
The gain that is applied to each sample will vary from | |||||
startGain on the first sample to endGain on the last Sample, | |||||
so it can be used to do basic fades. | |||||
For speed, this doesn't check whether the sample numbers | |||||
are in-range, so be careful! | |||||
*/ | |||||
void applyGainRamp (int startSample, | |||||
int numSamples, | |||||
float startGain, | |||||
float endGain) noexcept; | |||||
/** Adds samples from another buffer to this one. | |||||
@param destChannel the channel within this buffer to add the samples to | |||||
@param destStartSample the start sample within this buffer's channel | |||||
@param source the source buffer to add from | |||||
@param sourceChannel the channel within the source buffer to read from | |||||
@param sourceStartSample the offset within the source buffer's channel to start reading samples from | |||||
@param numSamples the number of samples to process | |||||
@param gainToApplyToSource an optional gain to apply to the source samples before they are | |||||
added to this buffer's samples | |||||
@see copyFrom | |||||
*/ | |||||
void addFrom (int destChannel, | |||||
int destStartSample, | |||||
const AudioSampleBuffer& source, | |||||
int sourceChannel, | |||||
int sourceStartSample, | |||||
int numSamples, | |||||
float gainToApplyToSource = 1.0f) noexcept; | |||||
/** Adds samples from an array of floats to one of the channels. | |||||
@param destChannel the channel within this buffer to add the samples to | |||||
@param destStartSample the start sample within this buffer's channel | |||||
@param source the source data to use | |||||
@param numSamples the number of samples to process | |||||
@param gainToApplyToSource an optional gain to apply to the source samples before they are | |||||
added to this buffer's samples | |||||
@see copyFrom | |||||
*/ | |||||
void addFrom (int destChannel, | |||||
int destStartSample, | |||||
const float* source, | |||||
int numSamples, | |||||
float gainToApplyToSource = 1.0f) noexcept; | |||||
/** Adds samples from an array of floats, applying a gain ramp to them. | |||||
@param destChannel the channel within this buffer to add the samples to | |||||
@param destStartSample the start sample within this buffer's channel | |||||
@param source the source data to use | |||||
@param numSamples the number of samples to process | |||||
@param startGain the gain to apply to the first sample (this is multiplied with | |||||
the source samples before they are added to this buffer) | |||||
@param endGain the gain to apply to the final sample. The gain is linearly | |||||
interpolated between the first and last samples. | |||||
*/ | |||||
void addFromWithRamp (int destChannel, | |||||
int destStartSample, | |||||
const float* source, | |||||
int numSamples, | |||||
float startGain, | |||||
float endGain) noexcept; | |||||
/** Copies samples from another buffer to this one. | |||||
@param destChannel the channel within this buffer to copy the samples to | |||||
@param destStartSample the start sample within this buffer's channel | |||||
@param source the source buffer to read from | |||||
@param sourceChannel the channel within the source buffer to read from | |||||
@param sourceStartSample the offset within the source buffer's channel to start reading samples from | |||||
@param numSamples the number of samples to process | |||||
@see addFrom | |||||
*/ | |||||
void copyFrom (int destChannel, | |||||
int destStartSample, | |||||
const AudioSampleBuffer& source, | |||||
int sourceChannel, | |||||
int sourceStartSample, | |||||
int numSamples) noexcept; | |||||
/** Copies samples from an array of floats into one of the channels. | |||||
@param destChannel the channel within this buffer to copy the samples to | |||||
@param destStartSample the start sample within this buffer's channel | |||||
@param source the source buffer to read from | |||||
@param numSamples the number of samples to process | |||||
@see addFrom | |||||
*/ | |||||
void copyFrom (int destChannel, | |||||
int destStartSample, | |||||
const float* source, | |||||
int numSamples) noexcept; | |||||
/** Copies samples from an array of floats into one of the channels, applying a gain to it. | |||||
@param destChannel the channel within this buffer to copy the samples to | |||||
@param destStartSample the start sample within this buffer's channel | |||||
@param source the source buffer to read from | |||||
@param numSamples the number of samples to process | |||||
@param gain the gain to apply | |||||
@see addFrom | |||||
*/ | |||||
void copyFrom (int destChannel, | |||||
int destStartSample, | |||||
const float* source, | |||||
int numSamples, | |||||
float gain) noexcept; | |||||
/** Copies samples from an array of floats into one of the channels, applying a gain ramp. | |||||
@param destChannel the channel within this buffer to copy the samples to | |||||
@param destStartSample the start sample within this buffer's channel | |||||
@param source the source buffer to read from | |||||
@param numSamples the number of samples to process | |||||
@param startGain the gain to apply to the first sample (this is multiplied with | |||||
the source samples before they are copied to this buffer) | |||||
@param endGain the gain to apply to the final sample. The gain is linearly | |||||
interpolated between the first and last samples. | |||||
@see addFrom | |||||
*/ | |||||
void copyFromWithRamp (int destChannel, | |||||
int destStartSample, | |||||
const float* source, | |||||
int numSamples, | |||||
float startGain, | |||||
float endGain) noexcept; | |||||
/** Finds the highest and lowest sample values in a given range. | |||||
@param channel the channel to read from | |||||
@param startSample the start sample within the channel | |||||
@param numSamples the number of samples to check | |||||
@param minVal on return, the lowest value that was found | |||||
@param maxVal on return, the highest value that was found | |||||
*/ | |||||
void findMinMax (int channel, | |||||
int startSample, | |||||
int numSamples, | |||||
float& minVal, | |||||
float& maxVal) const noexcept; | |||||
/** Finds the highest absolute sample value within a region of a channel. | |||||
*/ | |||||
float getMagnitude (int channel, | |||||
int startSample, | |||||
int numSamples) const noexcept; | |||||
/** Finds the highest absolute sample value within a region on all channels. | |||||
*/ | |||||
float getMagnitude (int startSample, | |||||
int numSamples) const noexcept; | |||||
/** Returns the root mean squared level for a region of a channel. | |||||
*/ | |||||
float getRMSLevel (int channel, | |||||
int startSample, | |||||
int numSamples) const noexcept; | |||||
private: | |||||
//============================================================================== | |||||
int numChannels, size; | |||||
size_t allocatedBytes; | |||||
float** channels; | |||||
HeapBlock <char, true> allocatedData; | |||||
float* preallocatedChannelSpace [32]; | |||||
void allocateData(); | |||||
void allocateChannels (float* const* dataToReferTo, int offset); | |||||
JUCE_LEAK_DETECTOR (AudioSampleBuffer) | |||||
}; | |||||
#endif // __JUCE_AUDIOSAMPLEBUFFER_JUCEHEADER__ |
@@ -0,0 +1,104 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_DECIBELS_JUCEHEADER__ | |||||
#define __JUCE_DECIBELS_JUCEHEADER__ | |||||
//============================================================================== | |||||
/** | |||||
This class contains some helpful static methods for dealing with decibel values. | |||||
*/ | |||||
class Decibels | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Converts a dBFS value to its equivalent gain level. | |||||
A gain of 1.0 = 0 dB, and lower gains map onto negative decibel values. Any | |||||
decibel value lower than minusInfinityDb will return a gain of 0. | |||||
*/ | |||||
template <typename Type> | |||||
static Type decibelsToGain (const Type decibels, | |||||
const Type minusInfinityDb = (Type) defaultMinusInfinitydB) | |||||
{ | |||||
return decibels > minusInfinityDb ? powf ((Type) 10.0, decibels * (Type) 0.05) | |||||
: Type(); | |||||
} | |||||
/** Converts a gain level into a dBFS value. | |||||
A gain of 1.0 = 0 dB, and lower gains map onto negative decibel values. | |||||
If the gain is 0 (or negative), then the method will return the value | |||||
provided as minusInfinityDb. | |||||
*/ | |||||
template <typename Type> | |||||
static Type gainToDecibels (const Type gain, | |||||
const Type minusInfinityDb = (Type) defaultMinusInfinitydB) | |||||
{ | |||||
return gain > Type() ? jmax (minusInfinityDb, (Type) std::log10 (gain) * (Type) 20.0) | |||||
: minusInfinityDb; | |||||
} | |||||
//============================================================================== | |||||
/** Converts a decibel reading to a string, with the 'dB' suffix. | |||||
If the decibel value is lower than minusInfinityDb, the return value will | |||||
be "-INF dB". | |||||
*/ | |||||
template <typename Type> | |||||
static String toString (const Type decibels, | |||||
const int decimalPlaces = 2, | |||||
const Type minusInfinityDb = (Type) defaultMinusInfinitydB) | |||||
{ | |||||
String s; | |||||
if (decibels <= minusInfinityDb) | |||||
{ | |||||
s = "-INF dB"; | |||||
} | |||||
else | |||||
{ | |||||
if (decibels >= Type()) | |||||
s << '+'; | |||||
s << String (decibels, decimalPlaces) << " dB"; | |||||
} | |||||
return s; | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
enum | |||||
{ | |||||
defaultMinusInfinitydB = -100 | |||||
}; | |||||
Decibels(); // This class can't be instantiated, it's just a holder for static methods.. | |||||
JUCE_DECLARE_NON_COPYABLE (Decibels) | |||||
}; | |||||
#endif // __JUCE_DECIBELS_JUCEHEADER__ |
@@ -0,0 +1,252 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce 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 | |||||
//============================================================================== | |||||
IIRFilter::IIRFilter() | |||||
: active (false) | |||||
{ | |||||
reset(); | |||||
} | |||||
IIRFilter::IIRFilter (const IIRFilter& other) | |||||
: active (other.active) | |||||
{ | |||||
const ScopedLock sl (other.processLock); | |||||
memcpy (coefficients, other.coefficients, sizeof (coefficients)); | |||||
reset(); | |||||
} | |||||
IIRFilter::~IIRFilter() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
void IIRFilter::reset() noexcept | |||||
{ | |||||
const ScopedLock sl (processLock); | |||||
x1 = 0; | |||||
x2 = 0; | |||||
y1 = 0; | |||||
y2 = 0; | |||||
} | |||||
float IIRFilter::processSingleSampleRaw (const float in) noexcept | |||||
{ | |||||
float out = coefficients[0] * in | |||||
+ coefficients[1] * x1 | |||||
+ coefficients[2] * x2 | |||||
- coefficients[4] * y1 | |||||
- coefficients[5] * y2; | |||||
JUCE_SNAP_TO_ZERO (out); | |||||
x2 = x1; | |||||
x1 = in; | |||||
y2 = y1; | |||||
y1 = out; | |||||
return out; | |||||
} | |||||
void IIRFilter::processSamples (float* const samples, | |||||
const int numSamples) noexcept | |||||
{ | |||||
const ScopedLock sl (processLock); | |||||
if (active) | |||||
{ | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
const float in = samples[i]; | |||||
float out = coefficients[0] * in | |||||
+ coefficients[1] * x1 | |||||
+ coefficients[2] * x2 | |||||
- coefficients[4] * y1 | |||||
- coefficients[5] * y2; | |||||
JUCE_SNAP_TO_ZERO (out); | |||||
x2 = x1; | |||||
x1 = in; | |||||
y2 = y1; | |||||
y1 = out; | |||||
samples[i] = out; | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void IIRFilter::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); | |||||
setCoefficients (c1, | |||||
c1 * 2.0f, | |||||
c1, | |||||
1.0, | |||||
c1 * 2.0 * (1.0 - nSquared), | |||||
c1 * (1.0 - std::sqrt (2.0) * n + nSquared)); | |||||
} | |||||
void IIRFilter::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); | |||||
setCoefficients (c1, | |||||
c1 * -2.0f, | |||||
c1, | |||||
1.0, | |||||
c1 * 2.0 * (nSquared - 1.0), | |||||
c1 * (1.0 - std::sqrt (2.0) * n + nSquared)); | |||||
} | |||||
void IIRFilter::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, 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; | |||||
setCoefficients (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); | |||||
} | |||||
void IIRFilter::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, 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; | |||||
setCoefficients (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); | |||||
} | |||||
void IIRFilter::makeBandPass (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, 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; | |||||
setCoefficients (1.0 + alphaTimesA, | |||||
c2, | |||||
1.0 - alphaTimesA, | |||||
1.0 + alphaOverA, | |||||
c2, | |||||
1.0 - alphaOverA); | |||||
} | |||||
void IIRFilter::makeInactive() noexcept | |||||
{ | |||||
const ScopedLock sl (processLock); | |||||
active = false; | |||||
} | |||||
//============================================================================== | |||||
void IIRFilter::copyCoefficientsFrom (const IIRFilter& other) noexcept | |||||
{ | |||||
const ScopedLock sl (processLock); | |||||
memcpy (coefficients, other.coefficients, sizeof (coefficients)); | |||||
active = other.active; | |||||
} | |||||
//============================================================================== | |||||
void IIRFilter::setCoefficients (double c1, double c2, double c3, | |||||
double c4, double c5, double c6) noexcept | |||||
{ | |||||
const double a = 1.0 / c4; | |||||
c1 *= a; | |||||
c2 *= a; | |||||
c3 *= a; | |||||
c5 *= a; | |||||
c6 *= a; | |||||
const ScopedLock sl (processLock); | |||||
coefficients[0] = (float) c1; | |||||
coefficients[1] = (float) c2; | |||||
coefficients[2] = (float) c3; | |||||
coefficients[3] = (float) c4; | |||||
coefficients[4] = (float) c5; | |||||
coefficients[5] = (float) c6; | |||||
active = true; | |||||
} | |||||
#undef JUCE_SNAP_TO_ZERO |
@@ -0,0 +1,149 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_IIRFILTER_JUCEHEADER__ | |||||
#define __JUCE_IIRFILTER_JUCEHEADER__ | |||||
//============================================================================== | |||||
/** | |||||
An IIR filter that can perform low, high, or band-pass filtering on an | |||||
audio signal. | |||||
@see 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 appropriate method to turn it into the type | |||||
of filter needed. | |||||
*/ | |||||
IIRFilter(); | |||||
/** Creates a copy of another filter. */ | |||||
IIRFilter (const IIRFilter& other); | |||||
/** Destructor. */ | |||||
~IIRFilter(); | |||||
//============================================================================== | |||||
/** 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; | |||||
//============================================================================== | |||||
/** Sets the filter up to act as a low-pass filter. | |||||
*/ | |||||
void makeLowPass (double sampleRate, | |||||
double frequency) noexcept; | |||||
/** Sets the filter up to act as a high-pass filter. | |||||
*/ | |||||
void makeHighPass (double sampleRate, | |||||
double frequency) noexcept; | |||||
//============================================================================== | |||||
/** Sets the filter up to act as 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. | |||||
*/ | |||||
void makeLowShelf (double sampleRate, | |||||
double cutOffFrequency, | |||||
double Q, | |||||
float gainFactor) noexcept; | |||||
/** Sets the filter up to act as 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. | |||||
*/ | |||||
void makeHighShelf (double sampleRate, | |||||
double cutOffFrequency, | |||||
double Q, | |||||
float gainFactor) noexcept; | |||||
/** Sets the filter up to act as a band pass filter centred around a | |||||
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. | |||||
*/ | |||||
void makeBandPass (double sampleRate, | |||||
double centreFrequency, | |||||
double Q, | |||||
float gainFactor) noexcept; | |||||
/** Clears the filter's coefficients so that it becomes inactive. | |||||
*/ | |||||
void makeInactive() noexcept; | |||||
//============================================================================== | |||||
/** Makes this filter duplicate the set-up of another one. | |||||
*/ | |||||
void copyCoefficientsFrom (const IIRFilter& other) noexcept; | |||||
protected: | |||||
//============================================================================== | |||||
CriticalSection processLock; | |||||
void setCoefficients (double c1, double c2, double c3, | |||||
double c4, double c5, double c6) noexcept; | |||||
bool active; | |||||
float coefficients[6]; | |||||
float x1, x2, y1, y2; | |||||
// (use the copyCoefficientsFrom() method instead of this operator) | |||||
IIRFilter& operator= (const IIRFilter&); | |||||
JUCE_LEAK_DETECTOR (IIRFilter) | |||||
}; | |||||
#endif // __JUCE_IIRFILTER_JUCEHEADER__ |
@@ -0,0 +1,321 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_REVERB_JUCEHEADER__ | |||||
#define __JUCE_REVERB_JUCEHEADER__ | |||||
//============================================================================== | |||||
/** | |||||
Performs a simple reverb effect on a stream of audio data. | |||||
This is a simple stereo reverb, based on the technique and tunings used in FreeVerb. | |||||
Use setSampleRate() to prepare it, and then call processStereo() or processMono() to | |||||
apply the reverb to your audio data. | |||||
@see ReverbAudioSource | |||||
*/ | |||||
class Reverb | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
Reverb() | |||||
{ | |||||
setParameters (Parameters()); | |||||
setSampleRate (44100.0); | |||||
} | |||||
//============================================================================== | |||||
/** Holds the parameters being used by a Reverb object. */ | |||||
struct Parameters | |||||
{ | |||||
Parameters() noexcept | |||||
: roomSize (0.5f), | |||||
damping (0.5f), | |||||
wetLevel (0.33f), | |||||
dryLevel (0.4f), | |||||
width (1.0f), | |||||
freezeMode (0) | |||||
{} | |||||
float roomSize; /**< Room size, 0 to 1.0, where 1.0 is big, 0 is small. */ | |||||
float damping; /**< Damping, 0 to 1.0, where 0 is not damped, 1.0 is fully damped. */ | |||||
float wetLevel; /**< Wet level, 0 to 1.0 */ | |||||
float dryLevel; /**< Dry level, 0 to 1.0 */ | |||||
float width; /**< Reverb width, 0 to 1.0, where 1.0 is very wide. */ | |||||
float freezeMode; /**< Freeze mode - values < 0.5 are "normal" mode, values > 0.5 | |||||
put the reverb into a continuous feedback loop. */ | |||||
}; | |||||
//============================================================================== | |||||
/** Returns the reverb's current parameters. */ | |||||
const Parameters& getParameters() const noexcept { return parameters; } | |||||
/** Applies a new set of parameters to the reverb. | |||||
Note that this doesn't attempt to lock the reverb, so if you call this in parallel with | |||||
the process method, you may get artifacts. | |||||
*/ | |||||
void setParameters (const Parameters& newParams) | |||||
{ | |||||
const float wetScaleFactor = 3.0f; | |||||
const float dryScaleFactor = 2.0f; | |||||
const float wet = newParams.wetLevel * wetScaleFactor; | |||||
wet1 = wet * (newParams.width * 0.5f + 0.5f); | |||||
wet2 = wet * (1.0f - newParams.width) * 0.5f; | |||||
dry = newParams.dryLevel * dryScaleFactor; | |||||
gain = isFrozen (newParams.freezeMode) ? 0.0f : 0.015f; | |||||
parameters = newParams; | |||||
shouldUpdateDamping = true; | |||||
} | |||||
//============================================================================== | |||||
/** Sets the sample rate that will be used for the reverb. | |||||
You must call this before the process methods, in order to tell it the correct sample rate. | |||||
*/ | |||||
void setSampleRate (const double sampleRate) | |||||
{ | |||||
jassert (sampleRate > 0); | |||||
static const short combTunings[] = { 1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617 }; // (at 44100Hz) | |||||
static const short allPassTunings[] = { 556, 441, 341, 225 }; | |||||
const int stereoSpread = 23; | |||||
const int intSampleRate = (int) sampleRate; | |||||
for (int i = 0; i < numCombs; ++i) | |||||
{ | |||||
comb[0][i].setSize ((intSampleRate * combTunings[i]) / 44100); | |||||
comb[1][i].setSize ((intSampleRate * (combTunings[i] + stereoSpread)) / 44100); | |||||
} | |||||
for (int i = 0; i < numAllPasses; ++i) | |||||
{ | |||||
allPass[0][i].setSize ((intSampleRate * allPassTunings[i]) / 44100); | |||||
allPass[1][i].setSize ((intSampleRate * (allPassTunings[i] + stereoSpread)) / 44100); | |||||
} | |||||
shouldUpdateDamping = true; | |||||
} | |||||
/** Clears the reverb's buffers. */ | |||||
void reset() | |||||
{ | |||||
for (int j = 0; j < numChannels; ++j) | |||||
{ | |||||
for (int i = 0; i < numCombs; ++i) | |||||
comb[j][i].clear(); | |||||
for (int i = 0; i < numAllPasses; ++i) | |||||
allPass[j][i].clear(); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
/** Applies the reverb to two stereo channels of audio data. */ | |||||
void processStereo (float* const left, float* const right, const int numSamples) noexcept | |||||
{ | |||||
jassert (left != nullptr && right != nullptr); | |||||
if (shouldUpdateDamping) | |||||
updateDamping(); | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
const float input = (left[i] + right[i]) * gain; | |||||
float outL = 0, outR = 0; | |||||
for (int j = 0; j < numCombs; ++j) // accumulate the comb filters in parallel | |||||
{ | |||||
outL += comb[0][j].process (input); | |||||
outR += comb[1][j].process (input); | |||||
} | |||||
for (int j = 0; j < numAllPasses; ++j) // run the allpass filters in series | |||||
{ | |||||
outL = allPass[0][j].process (outL); | |||||
outR = allPass[1][j].process (outR); | |||||
} | |||||
left[i] = outL * wet1 + outR * wet2 + left[i] * dry; | |||||
right[i] = outR * wet1 + outL * wet2 + right[i] * dry; | |||||
} | |||||
} | |||||
/** Applies the reverb to a single mono channel of audio data. */ | |||||
void processMono (float* const samples, const int numSamples) noexcept | |||||
{ | |||||
jassert (samples != nullptr); | |||||
if (shouldUpdateDamping) | |||||
updateDamping(); | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
const float input = samples[i] * gain; | |||||
float output = 0; | |||||
for (int j = 0; j < numCombs; ++j) // accumulate the comb filters in parallel | |||||
output += comb[0][j].process (input); | |||||
for (int j = 0; j < numAllPasses; ++j) // run the allpass filters in series | |||||
output = allPass[0][j].process (output); | |||||
samples[i] = output * wet1 + input * dry; | |||||
} | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
Parameters parameters; | |||||
volatile bool shouldUpdateDamping; | |||||
float gain, wet1, wet2, dry; | |||||
inline static bool isFrozen (const float freezeMode) noexcept { return freezeMode >= 0.5f; } | |||||
void updateDamping() noexcept | |||||
{ | |||||
const float roomScaleFactor = 0.28f; | |||||
const float roomOffset = 0.7f; | |||||
const float dampScaleFactor = 0.4f; | |||||
shouldUpdateDamping = false; | |||||
if (isFrozen (parameters.freezeMode)) | |||||
setDamping (0.0f, 1.0f); | |||||
else | |||||
setDamping (parameters.damping * dampScaleFactor, | |||||
parameters.roomSize * roomScaleFactor + roomOffset); | |||||
} | |||||
void setDamping (const float dampingToUse, const float roomSizeToUse) noexcept | |||||
{ | |||||
for (int j = 0; j < numChannels; ++j) | |||||
for (int i = numCombs; --i >= 0;) | |||||
comb[j][i].setFeedbackAndDamp (roomSizeToUse, dampingToUse); | |||||
} | |||||
//============================================================================== | |||||
class CombFilter | |||||
{ | |||||
public: | |||||
CombFilter() noexcept : bufferSize (0), bufferIndex (0) {} | |||||
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_JUCEHEADER__ |
@@ -0,0 +1,63 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#if defined (__JUCE_AUDIO_BASICS_JUCEHEADER__) && ! 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" | |||||
namespace juce | |||||
{ | |||||
// START_AUTOINCLUDE buffers/*.cpp, effects/*.cpp, midi/*.cpp, sources/*.cpp, synthesisers/*.cpp | |||||
#include "buffers/juce_AudioDataConverters.cpp" | |||||
#include "buffers/juce_AudioSampleBuffer.cpp" | |||||
#include "effects/juce_IIRFilter.cpp" | |||||
#include "midi/juce_MidiBuffer.cpp" | |||||
#include "midi/juce_MidiFile.cpp" | |||||
#include "midi/juce_MidiKeyboardState.cpp" | |||||
#include "midi/juce_MidiMessage.cpp" | |||||
#include "midi/juce_MidiMessageSequence.cpp" | |||||
#include "sources/juce_BufferingAudioSource.cpp" | |||||
#include "sources/juce_ChannelRemappingAudioSource.cpp" | |||||
#include "sources/juce_IIRFilterAudioSource.cpp" | |||||
#include "sources/juce_MixerAudioSource.cpp" | |||||
#include "sources/juce_ResamplingAudioSource.cpp" | |||||
#include "sources/juce_ReverbAudioSource.cpp" | |||||
#include "sources/juce_ToneGeneratorAudioSource.cpp" | |||||
#include "synthesisers/juce_Synthesiser.cpp" | |||||
// END_AUTOINCLUDE | |||||
} |
@@ -0,0 +1,100 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_AUDIO_BASICS_JUCEHEADER__ | |||||
#define __JUCE_AUDIO_BASICS_JUCEHEADER__ | |||||
#include "../juce_core/juce_core.h" | |||||
//============================================================================= | |||||
namespace juce | |||||
{ | |||||
// START_AUTOINCLUDE buffers, effects, midi, sources, synthesisers | |||||
#ifndef __JUCE_AUDIODATACONVERTERS_JUCEHEADER__ | |||||
#include "buffers/juce_AudioDataConverters.h" | |||||
#endif | |||||
#ifndef __JUCE_AUDIOSAMPLEBUFFER_JUCEHEADER__ | |||||
#include "buffers/juce_AudioSampleBuffer.h" | |||||
#endif | |||||
#ifndef __JUCE_DECIBELS_JUCEHEADER__ | |||||
#include "effects/juce_Decibels.h" | |||||
#endif | |||||
#ifndef __JUCE_IIRFILTER_JUCEHEADER__ | |||||
#include "effects/juce_IIRFilter.h" | |||||
#endif | |||||
#ifndef __JUCE_REVERB_JUCEHEADER__ | |||||
#include "effects/juce_Reverb.h" | |||||
#endif | |||||
#ifndef __JUCE_MIDIBUFFER_JUCEHEADER__ | |||||
#include "midi/juce_MidiBuffer.h" | |||||
#endif | |||||
#ifndef __JUCE_MIDIFILE_JUCEHEADER__ | |||||
#include "midi/juce_MidiFile.h" | |||||
#endif | |||||
#ifndef __JUCE_MIDIKEYBOARDSTATE_JUCEHEADER__ | |||||
#include "midi/juce_MidiKeyboardState.h" | |||||
#endif | |||||
#ifndef __JUCE_MIDIMESSAGE_JUCEHEADER__ | |||||
#include "midi/juce_MidiMessage.h" | |||||
#endif | |||||
#ifndef __JUCE_MIDIMESSAGESEQUENCE_JUCEHEADER__ | |||||
#include "midi/juce_MidiMessageSequence.h" | |||||
#endif | |||||
#ifndef __JUCE_AUDIOSOURCE_JUCEHEADER__ | |||||
#include "sources/juce_AudioSource.h" | |||||
#endif | |||||
#ifndef __JUCE_BUFFERINGAUDIOSOURCE_JUCEHEADER__ | |||||
#include "sources/juce_BufferingAudioSource.h" | |||||
#endif | |||||
#ifndef __JUCE_CHANNELREMAPPINGAUDIOSOURCE_JUCEHEADER__ | |||||
#include "sources/juce_ChannelRemappingAudioSource.h" | |||||
#endif | |||||
#ifndef __JUCE_IIRFILTERAUDIOSOURCE_JUCEHEADER__ | |||||
#include "sources/juce_IIRFilterAudioSource.h" | |||||
#endif | |||||
#ifndef __JUCE_MIXERAUDIOSOURCE_JUCEHEADER__ | |||||
#include "sources/juce_MixerAudioSource.h" | |||||
#endif | |||||
#ifndef __JUCE_POSITIONABLEAUDIOSOURCE_JUCEHEADER__ | |||||
#include "sources/juce_PositionableAudioSource.h" | |||||
#endif | |||||
#ifndef __JUCE_RESAMPLINGAUDIOSOURCE_JUCEHEADER__ | |||||
#include "sources/juce_ResamplingAudioSource.h" | |||||
#endif | |||||
#ifndef __JUCE_REVERBAUDIOSOURCE_JUCEHEADER__ | |||||
#include "sources/juce_ReverbAudioSource.h" | |||||
#endif | |||||
#ifndef __JUCE_TONEGENERATORAUDIOSOURCE_JUCEHEADER__ | |||||
#include "sources/juce_ToneGeneratorAudioSource.h" | |||||
#endif | |||||
#ifndef __JUCE_SYNTHESISER_JUCEHEADER__ | |||||
#include "synthesisers/juce_Synthesiser.h" | |||||
#endif | |||||
// END_AUTOINCLUDE | |||||
} | |||||
#endif // __JUCE_AUDIO_BASICS_JUCEHEADER__ |
@@ -0,0 +1,26 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#include "juce_audio_basics.cpp" |
@@ -0,0 +1,21 @@ | |||||
{ | |||||
"id": "juce_audio_basics", | |||||
"name": "JUCE audio and midi data classes", | |||||
"version": "2.0.32", | |||||
"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/*" ] | |||||
} |
@@ -0,0 +1,290 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
namespace MidiBufferHelpers | |||||
{ | |||||
inline int getEventTime (const void* const d) noexcept | |||||
{ | |||||
return *static_cast <const int*> (d); | |||||
} | |||||
inline uint16 getEventDataSize (const void* const d) noexcept | |||||
{ | |||||
return *reinterpret_cast <const uint16*> (static_cast <const char*> (d) + sizeof (int)); | |||||
} | |||||
inline uint16 getEventTotalSize (const void* const d) noexcept | |||||
{ | |||||
return getEventDataSize (d) + sizeof (int) + sizeof (uint16); | |||||
} | |||||
static int findActualEventLength (const uint8* const data, const int maxBytes) noexcept | |||||
{ | |||||
unsigned int byte = (unsigned int) *data; | |||||
int size = 0; | |||||
if (byte == 0xf0 || byte == 0xf7) | |||||
{ | |||||
const uint8* d = data + 1; | |||||
while (d < data + maxBytes) | |||||
if (*d++ == 0xf7) | |||||
break; | |||||
size = (int) (d - data); | |||||
} | |||||
else if (byte == 0xff) | |||||
{ | |||||
int n; | |||||
const int bytesLeft = MidiMessage::readVariableLengthVal (data + 1, n); | |||||
size = jmin (maxBytes, n + 2 + bytesLeft); | |||||
} | |||||
else if (byte >= 0x80) | |||||
{ | |||||
size = jmin (maxBytes, MidiMessage::getMessageLengthFromFirstByte ((uint8) byte)); | |||||
} | |||||
return size; | |||||
} | |||||
} | |||||
//============================================================================== | |||||
MidiBuffer::MidiBuffer() noexcept | |||||
: bytesUsed (0) | |||||
{ | |||||
} | |||||
MidiBuffer::MidiBuffer (const MidiMessage& message) noexcept | |||||
: bytesUsed (0) | |||||
{ | |||||
addEvent (message, 0); | |||||
} | |||||
MidiBuffer::MidiBuffer (const MidiBuffer& other) noexcept | |||||
: data (other.data), | |||||
bytesUsed (other.bytesUsed) | |||||
{ | |||||
} | |||||
MidiBuffer& MidiBuffer::operator= (const MidiBuffer& other) noexcept | |||||
{ | |||||
bytesUsed = other.bytesUsed; | |||||
data = other.data; | |||||
return *this; | |||||
} | |||||
void MidiBuffer::swapWith (MidiBuffer& other) noexcept | |||||
{ | |||||
data.swapWith (other.data); | |||||
std::swap (bytesUsed, other.bytesUsed); | |||||
} | |||||
MidiBuffer::~MidiBuffer() | |||||
{ | |||||
} | |||||
inline uint8* MidiBuffer::getData() const noexcept | |||||
{ | |||||
return static_cast <uint8*> (data.getData()); | |||||
} | |||||
void MidiBuffer::clear() noexcept | |||||
{ | |||||
bytesUsed = 0; | |||||
} | |||||
void MidiBuffer::clear (const int startSample, const int numSamples) | |||||
{ | |||||
uint8* const start = findEventAfter (getData(), startSample - 1); | |||||
uint8* const end = findEventAfter (start, startSample + numSamples - 1); | |||||
if (end > start) | |||||
{ | |||||
const int bytesToMove = bytesUsed - (int) (end - getData()); | |||||
if (bytesToMove > 0) | |||||
memmove (start, end, (size_t) bytesToMove); | |||||
bytesUsed -= (int) (end - start); | |||||
} | |||||
} | |||||
void MidiBuffer::addEvent (const MidiMessage& m, const int sampleNumber) | |||||
{ | |||||
addEvent (m.getRawData(), m.getRawDataSize(), sampleNumber); | |||||
} | |||||
void MidiBuffer::addEvent (const void* const newData, const int maxBytes, const int sampleNumber) | |||||
{ | |||||
const int numBytes = MidiBufferHelpers::findActualEventLength (static_cast <const uint8*> (newData), maxBytes); | |||||
if (numBytes > 0) | |||||
{ | |||||
size_t spaceNeeded = (size_t) bytesUsed + (size_t) numBytes + sizeof (int) + sizeof (uint16); | |||||
data.ensureSize ((spaceNeeded + spaceNeeded / 2 + 8) & ~(size_t) 7); | |||||
uint8* d = findEventAfter (getData(), sampleNumber); | |||||
const int bytesToMove = bytesUsed - (int) (d - getData()); | |||||
if (bytesToMove > 0) | |||||
memmove (d + numBytes + sizeof (int) + sizeof (uint16), d, (size_t) bytesToMove); | |||||
*reinterpret_cast <int*> (d) = sampleNumber; | |||||
d += sizeof (int); | |||||
*reinterpret_cast <uint16*> (d) = (uint16) numBytes; | |||||
d += sizeof (uint16); | |||||
memcpy (d, newData, (size_t) numBytes); | |||||
bytesUsed += sizeof (int) + sizeof (uint16) + (size_t) numBytes; | |||||
} | |||||
} | |||||
void MidiBuffer::addEvents (const MidiBuffer& otherBuffer, | |||||
const int startSample, | |||||
const int numSamples, | |||||
const int sampleDeltaToAdd) | |||||
{ | |||||
Iterator i (otherBuffer); | |||||
i.setNextSamplePosition (startSample); | |||||
const uint8* eventData; | |||||
int eventSize, position; | |||||
while (i.getNextEvent (eventData, eventSize, position) | |||||
&& (position < startSample + numSamples || numSamples < 0)) | |||||
{ | |||||
addEvent (eventData, eventSize, position + sampleDeltaToAdd); | |||||
} | |||||
} | |||||
void MidiBuffer::ensureSize (size_t minimumNumBytes) | |||||
{ | |||||
data.ensureSize (minimumNumBytes); | |||||
} | |||||
bool MidiBuffer::isEmpty() const noexcept | |||||
{ | |||||
return bytesUsed == 0; | |||||
} | |||||
int MidiBuffer::getNumEvents() const noexcept | |||||
{ | |||||
int n = 0; | |||||
const uint8* d = getData(); | |||||
const uint8* const end = d + bytesUsed; | |||||
while (d < end) | |||||
{ | |||||
d += MidiBufferHelpers::getEventTotalSize (d); | |||||
++n; | |||||
} | |||||
return n; | |||||
} | |||||
int MidiBuffer::getFirstEventTime() const noexcept | |||||
{ | |||||
return bytesUsed > 0 ? MidiBufferHelpers::getEventTime (data.getData()) : 0; | |||||
} | |||||
int MidiBuffer::getLastEventTime() const noexcept | |||||
{ | |||||
if (bytesUsed == 0) | |||||
return 0; | |||||
const uint8* d = getData(); | |||||
const uint8* const endData = d + bytesUsed; | |||||
for (;;) | |||||
{ | |||||
const uint8* const nextOne = d + MidiBufferHelpers::getEventTotalSize (d); | |||||
if (nextOne >= endData) | |||||
return MidiBufferHelpers::getEventTime (d); | |||||
d = nextOne; | |||||
} | |||||
} | |||||
uint8* MidiBuffer::findEventAfter (uint8* d, const int samplePosition) const noexcept | |||||
{ | |||||
const uint8* const endData = getData() + bytesUsed; | |||||
while (d < endData && MidiBufferHelpers::getEventTime (d) <= samplePosition) | |||||
d += MidiBufferHelpers::getEventTotalSize (d); | |||||
return d; | |||||
} | |||||
//============================================================================== | |||||
MidiBuffer::Iterator::Iterator (const MidiBuffer& buffer_) noexcept | |||||
: buffer (buffer_), | |||||
data (buffer_.getData()) | |||||
{ | |||||
} | |||||
MidiBuffer::Iterator::~Iterator() noexcept | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
void MidiBuffer::Iterator::setNextSamplePosition (const int samplePosition) noexcept | |||||
{ | |||||
data = buffer.getData(); | |||||
const uint8* dataEnd = data + buffer.bytesUsed; | |||||
while (data < dataEnd && MidiBufferHelpers::getEventTime (data) < samplePosition) | |||||
data += MidiBufferHelpers::getEventTotalSize (data); | |||||
} | |||||
bool MidiBuffer::Iterator::getNextEvent (const uint8* &midiData, int& numBytes, int& samplePosition) noexcept | |||||
{ | |||||
if (data >= buffer.getData() + buffer.bytesUsed) | |||||
return false; | |||||
samplePosition = MidiBufferHelpers::getEventTime (data); | |||||
numBytes = MidiBufferHelpers::getEventDataSize (data); | |||||
data += sizeof (int) + sizeof (uint16); | |||||
midiData = data; | |||||
data += numBytes; | |||||
return true; | |||||
} | |||||
bool MidiBuffer::Iterator::getNextEvent (MidiMessage& result, int& samplePosition) noexcept | |||||
{ | |||||
if (data >= buffer.getData() + buffer.bytesUsed) | |||||
return false; | |||||
samplePosition = MidiBufferHelpers::getEventTime (data); | |||||
const int numBytes = MidiBufferHelpers::getEventDataSize (data); | |||||
data += sizeof (int) + sizeof (uint16); | |||||
result = MidiMessage (data, numBytes, samplePosition); | |||||
data += numBytes; | |||||
return true; | |||||
} |
@@ -0,0 +1,241 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_MIDIBUFFER_JUCEHEADER__ | |||||
#define __JUCE_MIDIBUFFER_JUCEHEADER__ | |||||
#include "juce_MidiMessage.h" | |||||
//============================================================================== | |||||
/** | |||||
Holds a sequence of time-stamped midi events. | |||||
Analogous to the AudioSampleBuffer, this holds a set of midi events with | |||||
integer time-stamps. The buffer is kept sorted in order of the time-stamps. | |||||
If you're working with a sequence of midi events that may need to be manipulated | |||||
or read/written to a midi file, then MidiMessageSequence is probably a more | |||||
appropriate container. MidiBuffer is designed for lower-level streams of raw | |||||
midi data. | |||||
@see MidiMessage | |||||
*/ | |||||
class JUCE_API MidiBuffer | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an empty MidiBuffer. */ | |||||
MidiBuffer() noexcept; | |||||
/** Creates a MidiBuffer containing a single midi message. */ | |||||
explicit MidiBuffer (const MidiMessage& message) noexcept; | |||||
/** Creates a copy of another MidiBuffer. */ | |||||
MidiBuffer (const MidiBuffer& other) noexcept; | |||||
/** Makes a copy of another MidiBuffer. */ | |||||
MidiBuffer& operator= (const MidiBuffer& other) noexcept; | |||||
/** Destructor */ | |||||
~MidiBuffer(); | |||||
//============================================================================== | |||||
/** Removes all events from the buffer. */ | |||||
void clear() noexcept; | |||||
/** Removes all events between two times from the buffer. | |||||
All events for which (start <= event position < start + numSamples) will | |||||
be removed. | |||||
*/ | |||||
void clear (int start, int numSamples); | |||||
/** Returns true if the buffer is empty. | |||||
To actually retrieve the events, use a MidiBuffer::Iterator object | |||||
*/ | |||||
bool isEmpty() const noexcept; | |||||
/** Counts the number of events in the buffer. | |||||
This is actually quite a slow operation, as it has to iterate through all | |||||
the events, so you might prefer to call isEmpty() if that's all you need | |||||
to know. | |||||
*/ | |||||
int getNumEvents() const noexcept; | |||||
/** Adds an event to the buffer. | |||||
The sample number will be used to determine the position of the event in | |||||
the buffer, which is always kept sorted. The MidiMessage's timestamp is | |||||
ignored. | |||||
If an event is added whose sample position is the same as one or more events | |||||
already in the buffer, the new event will be placed after the existing ones. | |||||
To retrieve events, use a MidiBuffer::Iterator object | |||||
*/ | |||||
void addEvent (const MidiMessage& midiMessage, int sampleNumber); | |||||
/** Adds an event to the buffer from raw midi data. | |||||
The sample number will be used to determine the position of the event in | |||||
the buffer, which is always kept sorted. | |||||
If an event is added whose sample position is the same as one or more events | |||||
already in the buffer, the new event will be placed after the existing ones. | |||||
The event data will be inspected to calculate the number of bytes in length that | |||||
the midi event really takes up, so maxBytesOfMidiData may be longer than the data | |||||
that actually gets stored. E.g. if you pass in a note-on and a length of 4 bytes, | |||||
it'll actually only store 3 bytes. If the midi data is invalid, it might not | |||||
add an event at all. | |||||
To retrieve events, use a MidiBuffer::Iterator object | |||||
*/ | |||||
void addEvent (const void* rawMidiData, | |||||
int maxBytesOfMidiData, | |||||
int sampleNumber); | |||||
/** Adds some events from another buffer to this one. | |||||
@param otherBuffer the buffer containing the events you want to add | |||||
@param startSample the lowest sample number in the source buffer for which | |||||
events should be added. Any source events whose timestamp is | |||||
less than this will be ignored | |||||
@param numSamples the valid range of samples from the source buffer for which | |||||
events should be added - i.e. events in the source buffer whose | |||||
timestamp is greater than or equal to (startSample + numSamples) | |||||
will be ignored. If this value is less than 0, all events after | |||||
startSample will be taken. | |||||
@param sampleDeltaToAdd a value which will be added to the source timestamps of the events | |||||
that are added to this buffer | |||||
*/ | |||||
void addEvents (const MidiBuffer& otherBuffer, | |||||
int startSample, | |||||
int numSamples, | |||||
int sampleDeltaToAdd); | |||||
/** Returns the sample number of the first event in the buffer. | |||||
If the buffer's empty, this will just return 0. | |||||
*/ | |||||
int getFirstEventTime() const noexcept; | |||||
/** Returns the sample number of the last event in the buffer. | |||||
If the buffer's empty, this will just return 0. | |||||
*/ | |||||
int getLastEventTime() const noexcept; | |||||
//============================================================================== | |||||
/** Exchanges the contents of this buffer with another one. | |||||
This is a quick operation, because no memory allocating or copying is done, it | |||||
just swaps the internal state of the two buffers. | |||||
*/ | |||||
void swapWith (MidiBuffer& other) noexcept; | |||||
/** Preallocates some memory for the buffer to use. | |||||
This helps to avoid needing to reallocate space when the buffer has messages | |||||
added to it. | |||||
*/ | |||||
void ensureSize (size_t minimumNumBytes); | |||||
//============================================================================== | |||||
/** | |||||
Used to iterate through the events in a MidiBuffer. | |||||
Note that altering the buffer while an iterator is using it isn't a | |||||
safe operation. | |||||
@see MidiBuffer | |||||
*/ | |||||
class JUCE_API Iterator | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an Iterator for this MidiBuffer. */ | |||||
Iterator (const MidiBuffer& buffer) noexcept; | |||||
/** Destructor. */ | |||||
~Iterator() noexcept; | |||||
//============================================================================== | |||||
/** Repositions the iterator so that the next event retrieved will be the first | |||||
one whose sample position is at greater than or equal to the given position. | |||||
*/ | |||||
void setNextSamplePosition (int samplePosition) noexcept; | |||||
/** Retrieves a copy of the next event from the buffer. | |||||
@param result on return, this will be the message (the MidiMessage's timestamp | |||||
is not set) | |||||
@param samplePosition on return, this will be the position of the event | |||||
@returns true if an event was found, or false if the iterator has reached | |||||
the end of the buffer | |||||
*/ | |||||
bool getNextEvent (MidiMessage& result, | |||||
int& samplePosition) noexcept; | |||||
/** Retrieves the next event from the buffer. | |||||
@param midiData on return, this pointer will be set to a block of data containing | |||||
the midi message. Note that to make it fast, this is a pointer | |||||
directly into the MidiBuffer's internal data, so is only valid | |||||
temporarily until the MidiBuffer is altered. | |||||
@param numBytesOfMidiData on return, this is the number of bytes of data used by the | |||||
midi message | |||||
@param samplePosition on return, this will be the position of the event | |||||
@returns true if an event was found, or false if the iterator has reached | |||||
the end of the buffer | |||||
*/ | |||||
bool getNextEvent (const uint8* &midiData, | |||||
int& numBytesOfMidiData, | |||||
int& samplePosition) noexcept; | |||||
private: | |||||
//============================================================================== | |||||
const MidiBuffer& buffer; | |||||
const uint8* data; | |||||
JUCE_DECLARE_NON_COPYABLE (Iterator) | |||||
}; | |||||
private: | |||||
//============================================================================== | |||||
friend class MidiBuffer::Iterator; | |||||
MemoryBlock data; | |||||
int bytesUsed; | |||||
uint8* getData() const noexcept; | |||||
uint8* findEventAfter (uint8*, int samplePosition) const noexcept; | |||||
JUCE_LEAK_DETECTOR (MidiBuffer) | |||||
}; | |||||
#endif // __JUCE_MIDIBUFFER_JUCEHEADER__ |
@@ -0,0 +1,425 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
namespace MidiFileHelpers | |||||
{ | |||||
static void writeVariableLengthInt (OutputStream& out, unsigned int v) | |||||
{ | |||||
unsigned int buffer = v & 0x7f; | |||||
while ((v >>= 7) != 0) | |||||
{ | |||||
buffer <<= 8; | |||||
buffer |= ((v & 0x7f) | 0x80); | |||||
} | |||||
for (;;) | |||||
{ | |||||
out.writeByte ((char) buffer); | |||||
if (buffer & 0x80) | |||||
buffer >>= 8; | |||||
else | |||||
break; | |||||
} | |||||
} | |||||
static bool parseMidiHeader (const uint8* &data, short& timeFormat, short& fileType, short& numberOfTracks) noexcept | |||||
{ | |||||
unsigned int ch = ByteOrder::bigEndianInt (data); | |||||
data += 4; | |||||
if (ch != ByteOrder::bigEndianInt ("MThd")) | |||||
{ | |||||
bool ok = false; | |||||
if (ch == ByteOrder::bigEndianInt ("RIFF")) | |||||
{ | |||||
for (int i = 0; i < 8; ++i) | |||||
{ | |||||
ch = ByteOrder::bigEndianInt (data); | |||||
data += 4; | |||||
if (ch == ByteOrder::bigEndianInt ("MThd")) | |||||
{ | |||||
ok = true; | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
if (! ok) | |||||
return false; | |||||
} | |||||
unsigned int bytesRemaining = ByteOrder::bigEndianInt (data); | |||||
data += 4; | |||||
fileType = (short) ByteOrder::bigEndianShort (data); | |||||
data += 2; | |||||
numberOfTracks = (short) ByteOrder::bigEndianShort (data); | |||||
data += 2; | |||||
timeFormat = (short) ByteOrder::bigEndianShort (data); | |||||
data += 2; | |||||
bytesRemaining -= 6; | |||||
data += bytesRemaining; | |||||
return true; | |||||
} | |||||
static double convertTicksToSeconds (const double time, | |||||
const MidiMessageSequence& tempoEvents, | |||||
const int timeFormat) | |||||
{ | |||||
if (timeFormat < 0) | |||||
return time / (-(timeFormat >> 8) * (timeFormat & 0xff)); | |||||
double lastTime = 0.0, correctedTime = 0.0; | |||||
const double tickLen = 1.0 / (timeFormat & 0x7fff); | |||||
double secsPerTick = 0.5 * tickLen; | |||||
const int numEvents = tempoEvents.getNumEvents(); | |||||
for (int i = 0; i < numEvents; ++i) | |||||
{ | |||||
const MidiMessage& m = tempoEvents.getEventPointer(i)->message; | |||||
const double eventTime = m.getTimeStamp(); | |||||
if (eventTime >= time) | |||||
break; | |||||
correctedTime += (eventTime - lastTime) * secsPerTick; | |||||
lastTime = eventTime; | |||||
if (m.isTempoMetaEvent()) | |||||
secsPerTick = tickLen * m.getTempoSecondsPerQuarterNote(); | |||||
while (i + 1 < numEvents) | |||||
{ | |||||
const MidiMessage& m2 = tempoEvents.getEventPointer(i + 1)->message; | |||||
if (m2.getTimeStamp() != eventTime) | |||||
break; | |||||
if (m2.isTempoMetaEvent()) | |||||
secsPerTick = tickLen * m2.getTempoSecondsPerQuarterNote(); | |||||
++i; | |||||
} | |||||
} | |||||
return correctedTime + (time - lastTime) * secsPerTick; | |||||
} | |||||
// a comparator that puts all the note-offs before note-ons that have the same time | |||||
struct Sorter | |||||
{ | |||||
static int compareElements (const MidiMessageSequence::MidiEventHolder* const first, | |||||
const MidiMessageSequence::MidiEventHolder* const second) noexcept | |||||
{ | |||||
const double diff = (first->message.getTimeStamp() - second->message.getTimeStamp()); | |||||
if (diff > 0) return 1; | |||||
if (diff < 0) return -1; | |||||
if (first->message.isNoteOff() && second->message.isNoteOn()) return -1; | |||||
if (first->message.isNoteOn() && second->message.isNoteOff()) return 1; | |||||
return 0; | |||||
} | |||||
}; | |||||
} | |||||
//============================================================================== | |||||
MidiFile::MidiFile() | |||||
: timeFormat ((short) (unsigned short) 0xe728) | |||||
{ | |||||
} | |||||
MidiFile::~MidiFile() | |||||
{ | |||||
} | |||||
void MidiFile::clear() | |||||
{ | |||||
tracks.clear(); | |||||
} | |||||
//============================================================================== | |||||
int MidiFile::getNumTracks() const noexcept | |||||
{ | |||||
return tracks.size(); | |||||
} | |||||
const MidiMessageSequence* MidiFile::getTrack (const int index) const noexcept | |||||
{ | |||||
return tracks [index]; | |||||
} | |||||
void MidiFile::addTrack (const MidiMessageSequence& trackSequence) | |||||
{ | |||||
tracks.add (new MidiMessageSequence (trackSequence)); | |||||
} | |||||
//============================================================================== | |||||
short MidiFile::getTimeFormat() const noexcept | |||||
{ | |||||
return timeFormat; | |||||
} | |||||
void MidiFile::setTicksPerQuarterNote (const int ticks) noexcept | |||||
{ | |||||
timeFormat = (short) ticks; | |||||
} | |||||
void MidiFile::setSmpteTimeFormat (const int framesPerSecond, | |||||
const int subframeResolution) noexcept | |||||
{ | |||||
timeFormat = (short) (((-framesPerSecond) << 8) | subframeResolution); | |||||
} | |||||
//============================================================================== | |||||
void MidiFile::findAllTempoEvents (MidiMessageSequence& tempoChangeEvents) const | |||||
{ | |||||
for (int i = tracks.size(); --i >= 0;) | |||||
{ | |||||
const int numEvents = tracks.getUnchecked(i)->getNumEvents(); | |||||
for (int j = 0; j < numEvents; ++j) | |||||
{ | |||||
const MidiMessage& m = tracks.getUnchecked(i)->getEventPointer (j)->message; | |||||
if (m.isTempoMetaEvent()) | |||||
tempoChangeEvents.addEvent (m); | |||||
} | |||||
} | |||||
} | |||||
void MidiFile::findAllTimeSigEvents (MidiMessageSequence& timeSigEvents) const | |||||
{ | |||||
for (int i = tracks.size(); --i >= 0;) | |||||
{ | |||||
const int numEvents = tracks.getUnchecked(i)->getNumEvents(); | |||||
for (int j = 0; j < numEvents; ++j) | |||||
{ | |||||
const MidiMessage& m = tracks.getUnchecked(i)->getEventPointer (j)->message; | |||||
if (m.isTimeSignatureMetaEvent()) | |||||
timeSigEvents.addEvent (m); | |||||
} | |||||
} | |||||
} | |||||
double MidiFile::getLastTimestamp() const | |||||
{ | |||||
double t = 0.0; | |||||
for (int i = tracks.size(); --i >= 0;) | |||||
t = jmax (t, tracks.getUnchecked(i)->getEndTime()); | |||||
return t; | |||||
} | |||||
//============================================================================== | |||||
bool MidiFile::readFrom (InputStream& sourceStream) | |||||
{ | |||||
clear(); | |||||
MemoryBlock data; | |||||
const int maxSensibleMidiFileSize = 2 * 1024 * 1024; | |||||
// (put a sanity-check on the file size, as midi files are generally small) | |||||
if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize)) | |||||
{ | |||||
size_t size = data.getSize(); | |||||
const uint8* d = static_cast <const uint8*> (data.getData()); | |||||
short fileType, expectedTracks; | |||||
if (size > 16 && MidiFileHelpers::parseMidiHeader (d, timeFormat, fileType, expectedTracks)) | |||||
{ | |||||
size -= (size_t) (d - static_cast <const uint8*> (data.getData())); | |||||
int track = 0; | |||||
while (size > 0 && track < expectedTracks) | |||||
{ | |||||
const int chunkType = (int) ByteOrder::bigEndianInt (d); | |||||
d += 4; | |||||
const int chunkSize = (int) ByteOrder::bigEndianInt (d); | |||||
d += 4; | |||||
if (chunkSize <= 0) | |||||
break; | |||||
if (chunkType == (int) ByteOrder::bigEndianInt ("MTrk")) | |||||
readNextTrack (d, chunkSize); | |||||
size -= (size_t) chunkSize + 8; | |||||
d += chunkSize; | |||||
++track; | |||||
} | |||||
return true; | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
void MidiFile::readNextTrack (const uint8* data, int size) | |||||
{ | |||||
double time = 0; | |||||
uint8 lastStatusByte = 0; | |||||
MidiMessageSequence result; | |||||
while (size > 0) | |||||
{ | |||||
int bytesUsed; | |||||
const int delay = MidiMessage::readVariableLengthVal (data, bytesUsed); | |||||
data += bytesUsed; | |||||
size -= bytesUsed; | |||||
time += delay; | |||||
int messSize = 0; | |||||
const MidiMessage mm (data, size, messSize, lastStatusByte, time); | |||||
if (messSize <= 0) | |||||
break; | |||||
size -= messSize; | |||||
data += messSize; | |||||
result.addEvent (mm); | |||||
const uint8 firstByte = *(mm.getRawData()); | |||||
if ((firstByte & 0xf0) != 0xf0) | |||||
lastStatusByte = firstByte; | |||||
} | |||||
// use a sort that puts all the note-offs before note-ons that have the same time | |||||
MidiFileHelpers::Sorter sorter; | |||||
result.list.sort (sorter, true); | |||||
addTrack (result); | |||||
tracks.getLast()->updateMatchedPairs(); | |||||
} | |||||
//============================================================================== | |||||
void MidiFile::convertTimestampTicksToSeconds() | |||||
{ | |||||
MidiMessageSequence tempoEvents; | |||||
findAllTempoEvents (tempoEvents); | |||||
findAllTimeSigEvents (tempoEvents); | |||||
if (timeFormat != 0) | |||||
{ | |||||
for (int i = 0; i < tracks.size(); ++i) | |||||
{ | |||||
const MidiMessageSequence& ms = *tracks.getUnchecked(i); | |||||
for (int j = ms.getNumEvents(); --j >= 0;) | |||||
{ | |||||
MidiMessage& m = ms.getEventPointer(j)->message; | |||||
m.setTimeStamp (MidiFileHelpers::convertTicksToSeconds (m.getTimeStamp(), | |||||
tempoEvents, | |||||
timeFormat)); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
bool MidiFile::writeTo (OutputStream& out) | |||||
{ | |||||
out.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MThd")); | |||||
out.writeIntBigEndian (6); | |||||
out.writeShortBigEndian (1); // type | |||||
out.writeShortBigEndian ((short) tracks.size()); | |||||
out.writeShortBigEndian (timeFormat); | |||||
for (int i = 0; i < tracks.size(); ++i) | |||||
writeTrack (out, i); | |||||
out.flush(); | |||||
return true; | |||||
} | |||||
void MidiFile::writeTrack (OutputStream& mainOut, const int trackNum) | |||||
{ | |||||
MemoryOutputStream out; | |||||
const MidiMessageSequence& ms = *tracks.getUnchecked (trackNum); | |||||
int lastTick = 0; | |||||
uint8 lastStatusByte = 0; | |||||
for (int i = 0; i < ms.getNumEvents(); ++i) | |||||
{ | |||||
const MidiMessage& mm = ms.getEventPointer(i)->message; | |||||
if (! mm.isEndOfTrackMetaEvent()) | |||||
{ | |||||
const int tick = roundToInt (mm.getTimeStamp()); | |||||
const int delta = jmax (0, tick - lastTick); | |||||
MidiFileHelpers::writeVariableLengthInt (out, (uint32) delta); | |||||
lastTick = tick; | |||||
const uint8* data = mm.getRawData(); | |||||
int dataSize = mm.getRawDataSize(); | |||||
const uint8 statusByte = data[0]; | |||||
if (statusByte == lastStatusByte | |||||
&& (statusByte & 0xf0) != 0xf0 | |||||
&& dataSize > 1 | |||||
&& i > 0) | |||||
{ | |||||
++data; | |||||
--dataSize; | |||||
} | |||||
else if (statusByte == 0xf0) // Write sysex message with length bytes. | |||||
{ | |||||
out.writeByte ((char) statusByte); | |||||
++data; | |||||
--dataSize; | |||||
MidiFileHelpers::writeVariableLengthInt (out, (uint32) dataSize); | |||||
} | |||||
out.write (data, dataSize); | |||||
lastStatusByte = statusByte; | |||||
} | |||||
} | |||||
{ | |||||
out.writeByte (0); // (tick delta) | |||||
const MidiMessage m (MidiMessage::endOfTrack()); | |||||
out.write (m.getRawData(), m.getRawDataSize()); | |||||
} | |||||
mainOut.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MTrk")); | |||||
mainOut.writeIntBigEndian ((int) out.getDataSize()); | |||||
mainOut << out; | |||||
} |
@@ -0,0 +1,187 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_MIDIFILE_JUCEHEADER__ | |||||
#define __JUCE_MIDIFILE_JUCEHEADER__ | |||||
#include "juce_MidiMessageSequence.h" | |||||
//============================================================================== | |||||
/** | |||||
Reads/writes standard midi format files. | |||||
To read a midi file, create a MidiFile object and call its readFrom() method. You | |||||
can then get the individual midi tracks from it using the getTrack() method. | |||||
To write a file, create a MidiFile object, add some MidiMessageSequence objects | |||||
to it using the addTrack() method, and then call its writeTo() method to stream | |||||
it out. | |||||
@see MidiMessageSequence | |||||
*/ | |||||
class JUCE_API MidiFile | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an empty MidiFile object. | |||||
*/ | |||||
MidiFile(); | |||||
/** Destructor. */ | |||||
~MidiFile(); | |||||
//============================================================================== | |||||
/** Returns the number of tracks in the file. | |||||
@see getTrack, addTrack | |||||
*/ | |||||
int getNumTracks() const noexcept; | |||||
/** Returns a pointer to one of the tracks in the file. | |||||
@returns a pointer to the track, or nullptr if the index is out-of-range | |||||
@see getNumTracks, addTrack | |||||
*/ | |||||
const MidiMessageSequence* getTrack (int index) const noexcept; | |||||
/** Adds a midi track to the file. | |||||
This will make its own internal copy of the sequence that is passed-in. | |||||
@see getNumTracks, getTrack | |||||
*/ | |||||
void addTrack (const MidiMessageSequence& trackSequence); | |||||
/** Removes all midi tracks from the file. | |||||
@see getNumTracks | |||||
*/ | |||||
void clear(); | |||||
/** Returns the raw time format code that will be written to a stream. | |||||
After reading a midi file, this method will return the time-format that | |||||
was read from the file's header. It can be changed using the setTicksPerQuarterNote() | |||||
or setSmpteTimeFormat() methods. | |||||
If the value returned is positive, it indicates the number of midi ticks | |||||
per quarter-note - see setTicksPerQuarterNote(). | |||||
It it's negative, the upper byte indicates the frames-per-second (but negative), and | |||||
the lower byte is the number of ticks per frame - see setSmpteTimeFormat(). | |||||
*/ | |||||
short getTimeFormat() const noexcept; | |||||
/** Sets the time format to use when this file is written to a stream. | |||||
If this is called, the file will be written as bars/beats using the | |||||
specified resolution, rather than SMPTE absolute times, as would be | |||||
used if setSmpteTimeFormat() had been called instead. | |||||
@param ticksPerQuarterNote e.g. 96, 960 | |||||
@see setSmpteTimeFormat | |||||
*/ | |||||
void setTicksPerQuarterNote (int ticksPerQuarterNote) noexcept; | |||||
/** Sets the time format to use when this file is written to a stream. | |||||
If this is called, the file will be written using absolute times, rather | |||||
than bars/beats as would be the case if setTicksPerBeat() had been called | |||||
instead. | |||||
@param framesPerSecond must be 24, 25, 29 or 30 | |||||
@param subframeResolution the sub-second resolution, e.g. 4 (midi time code), | |||||
8, 10, 80 (SMPTE bit resolution), or 100. For millisecond | |||||
timing, setSmpteTimeFormat (25, 40) | |||||
@see setTicksPerBeat | |||||
*/ | |||||
void setSmpteTimeFormat (int framesPerSecond, | |||||
int subframeResolution) noexcept; | |||||
//============================================================================== | |||||
/** Makes a list of all the tempo-change meta-events from all tracks in the midi file. | |||||
Useful for finding the positions of all the tempo changes in a file. | |||||
@param tempoChangeEvents a list to which all the events will be added | |||||
*/ | |||||
void findAllTempoEvents (MidiMessageSequence& tempoChangeEvents) const; | |||||
/** Makes a list of all the time-signature meta-events from all tracks in the midi file. | |||||
Useful for finding the positions of all the tempo changes in a file. | |||||
@param timeSigEvents a list to which all the events will be added | |||||
*/ | |||||
void findAllTimeSigEvents (MidiMessageSequence& timeSigEvents) const; | |||||
/** Returns the latest timestamp in any of the tracks. | |||||
(Useful for finding the length of the file). | |||||
*/ | |||||
double getLastTimestamp() const; | |||||
//============================================================================== | |||||
/** Reads a midi file format stream. | |||||
After calling this, you can get the tracks that were read from the file by using the | |||||
getNumTracks() and getTrack() methods. | |||||
The timestamps of the midi events in the tracks will represent their positions in | |||||
terms of midi ticks. To convert them to seconds, use the convertTimestampTicksToSeconds() | |||||
method. | |||||
@returns true if the stream was read successfully | |||||
*/ | |||||
bool readFrom (InputStream& sourceStream); | |||||
/** Writes the midi tracks as a standard midi file. | |||||
@returns true if the operation succeeded. | |||||
*/ | |||||
bool writeTo (OutputStream& destStream); | |||||
/** Converts the timestamp of all the midi events from midi ticks to seconds. | |||||
This will use the midi time format and tempo/time signature info in the | |||||
tracks to convert all the timestamps to absolute values in seconds. | |||||
*/ | |||||
void convertTimestampTicksToSeconds(); | |||||
private: | |||||
//============================================================================== | |||||
OwnedArray <MidiMessageSequence> tracks; | |||||
short timeFormat; | |||||
void readNextTrack (const uint8* data, int size); | |||||
void writeTrack (OutputStream& mainOut, int trackNum); | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiFile) | |||||
}; | |||||
#endif // __JUCE_MIDIFILE_JUCEHEADER__ |
@@ -0,0 +1,184 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce 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,209 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_MIDIKEYBOARDSTATE_JUCEHEADER__ | |||||
#define __JUCE_MIDIKEYBOARDSTATE_JUCEHEADER__ | |||||
#include "juce_MidiBuffer.h" | |||||
class MidiKeyboardState; | |||||
//============================================================================== | |||||
/** | |||||
Receives events from a MidiKeyboardState object. | |||||
@see MidiKeyboardState | |||||
*/ | |||||
class JUCE_API MidiKeyboardStateListener | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
MidiKeyboardStateListener() noexcept {} | |||||
virtual ~MidiKeyboardStateListener() {} | |||||
//============================================================================== | |||||
/** Called when one of the MidiKeyboardState's keys is pressed. | |||||
This will be called synchronously when the state is either processing a | |||||
buffer in its MidiKeyboardState::processNextMidiBuffer() method, or | |||||
when a note is being played with its MidiKeyboardState::noteOn() method. | |||||
Note that this callback could happen from an audio callback thread, so be | |||||
careful not to block, and avoid any UI activity in the callback. | |||||
*/ | |||||
virtual void handleNoteOn (MidiKeyboardState* source, | |||||
int midiChannel, int midiNoteNumber, float velocity) = 0; | |||||
/** Called when one of the MidiKeyboardState's keys is released. | |||||
This will be called synchronously when the state is either processing a | |||||
buffer in its MidiKeyboardState::processNextMidiBuffer() method, or | |||||
when a note is being played with its MidiKeyboardState::noteOff() method. | |||||
Note that this callback could happen from an audio callback thread, so be | |||||
careful not to block, and avoid any UI activity in the callback. | |||||
*/ | |||||
virtual void handleNoteOff (MidiKeyboardState* source, | |||||
int midiChannel, int midiNoteNumber) = 0; | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
Represents a piano keyboard, keeping track of which keys are currently pressed. | |||||
This object can parse a stream of midi events, using them to update its idea | |||||
of which keys are pressed for each individiual midi channel. | |||||
When keys go up or down, it can broadcast these events to listener objects. | |||||
It also allows key up/down events to be triggered with its noteOn() and noteOff() | |||||
methods, and midi messages for these events will be merged into the | |||||
midi stream that gets processed by processNextMidiBuffer(). | |||||
*/ | |||||
class JUCE_API MidiKeyboardState | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
MidiKeyboardState(); | |||||
~MidiKeyboardState(); | |||||
//============================================================================== | |||||
/** Resets the state of the object. | |||||
All internal data for all the channels is reset, but no events are sent as a | |||||
result. | |||||
If you want to release any keys that are currently down, and to send out note-up | |||||
midi messages for this, use the allNotesOff() method instead. | |||||
*/ | |||||
void reset(); | |||||
/** Returns true if the given midi key is currently held down for the given midi channel. | |||||
The channel number must be between 1 and 16. If you want to see if any notes are | |||||
on for a range of channels, use the isNoteOnForChannels() method. | |||||
*/ | |||||
bool isNoteOn (int midiChannel, int midiNoteNumber) const noexcept; | |||||
/** Returns true if the given midi key is currently held down on any of a set of midi channels. | |||||
The channel mask has a bit set for each midi channel you want to test for - bit | |||||
0 = midi channel 1, bit 1 = midi channel 2, etc. | |||||
If a note is on for at least one of the specified channels, this returns true. | |||||
*/ | |||||
bool isNoteOnForChannels (int midiChannelMask, int midiNoteNumber) const noexcept; | |||||
/** Turns a specified note on. | |||||
This will cause a suitable midi note-on event to be injected into the midi buffer during the | |||||
next call to processNextMidiBuffer(). | |||||
It will also trigger a synchronous callback to the listeners to tell them that the key has | |||||
gone down. | |||||
*/ | |||||
void noteOn (int midiChannel, int midiNoteNumber, float velocity); | |||||
/** Turns a specified note off. | |||||
This will cause a suitable midi note-off event to be injected into the midi buffer during the | |||||
next call to processNextMidiBuffer(). | |||||
It will also trigger a synchronous callback to the listeners to tell them that the key has | |||||
gone up. | |||||
But if the note isn't acutally down for the given channel, this method will in fact do nothing. | |||||
*/ | |||||
void noteOff (int midiChannel, int midiNoteNumber); | |||||
/** This will turn off any currently-down notes for the given midi channel. | |||||
If you pass 0 for the midi channel, it will in fact turn off all notes on all channels. | |||||
Calling this method will make calls to noteOff(), so can trigger synchronous callbacks | |||||
and events being added to the midi stream. | |||||
*/ | |||||
void allNotesOff (int midiChannel); | |||||
//============================================================================== | |||||
/** Looks at a key-up/down event and uses it to update the state of this object. | |||||
To process a buffer full of midi messages, use the processNextMidiBuffer() method | |||||
instead. | |||||
*/ | |||||
void processNextMidiEvent (const MidiMessage& message); | |||||
/** Scans a midi stream for up/down events and adds its own events to it. | |||||
This will look for any up/down events and use them to update the internal state, | |||||
synchronously making suitable callbacks to the listeners. | |||||
If injectIndirectEvents is true, then midi events to produce the recent noteOn() | |||||
and noteOff() calls will be added into the buffer. | |||||
Only the section of the buffer whose timestamps are between startSample and | |||||
(startSample + numSamples) will be affected, and any events added will be placed | |||||
between these times. | |||||
If you're going to use this method, you'll need to keep calling it regularly for | |||||
it to work satisfactorily. | |||||
To process a single midi event at a time, use the processNextMidiEvent() method | |||||
instead. | |||||
*/ | |||||
void processNextMidiBuffer (MidiBuffer& buffer, | |||||
int startSample, | |||||
int numSamples, | |||||
bool injectIndirectEvents); | |||||
//============================================================================== | |||||
/** Registers a listener for callbacks when keys go up or down. | |||||
@see removeListener | |||||
*/ | |||||
void addListener (MidiKeyboardStateListener* listener); | |||||
/** Deregisters a listener. | |||||
@see addListener | |||||
*/ | |||||
void removeListener (MidiKeyboardStateListener* listener); | |||||
private: | |||||
//============================================================================== | |||||
CriticalSection lock; | |||||
uint16 noteStates [128]; | |||||
MidiBuffer eventsToAdd; | |||||
Array <MidiKeyboardStateListener*> listeners; | |||||
void noteOnInternal (int midiChannel, int midiNoteNumber, float velocity); | |||||
void noteOffInternal (int midiChannel, int midiNoteNumber); | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiKeyboardState) | |||||
}; | |||||
#endif // __JUCE_MIDIKEYBOARDSTATE_JUCEHEADER__ |
@@ -0,0 +1,940 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_MIDIMESSAGE_JUCEHEADER__ | |||||
#define __JUCE_MIDIMESSAGE_JUCEHEADER__ | |||||
//============================================================================== | |||||
/** | |||||
Encapsulates a MIDI message. | |||||
@see MidiMessageSequence, MidiOutput, MidiInput | |||||
*/ | |||||
class JUCE_API MidiMessage | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a 3-byte short midi message. | |||||
@param byte1 message byte 1 | |||||
@param byte2 message byte 2 | |||||
@param byte3 message byte 3 | |||||
@param timeStamp the time to give the midi message - this value doesn't | |||||
use any particular units, so will be application-specific | |||||
*/ | |||||
MidiMessage (int byte1, int byte2, int byte3, double timeStamp = 0) noexcept; | |||||
/** Creates a 2-byte short midi message. | |||||
@param byte1 message byte 1 | |||||
@param byte2 message byte 2 | |||||
@param timeStamp the time to give the midi message - this value doesn't | |||||
use any particular units, so will be application-specific | |||||
*/ | |||||
MidiMessage (int byte1, int byte2, double timeStamp = 0) noexcept; | |||||
/** Creates a 1-byte short midi message. | |||||
@param byte1 message byte 1 | |||||
@param timeStamp the time to give the midi message - this value doesn't | |||||
use any particular units, so will be application-specific | |||||
*/ | |||||
MidiMessage (int byte1, double timeStamp = 0) noexcept; | |||||
/** Creates a midi message from a block of data. */ | |||||
MidiMessage (const void* data, int numBytes, double timeStamp = 0); | |||||
/** Reads the next midi message from some data. | |||||
This will read as many bytes from a data stream as it needs to make a | |||||
complete message, and will return the number of bytes it used. This lets | |||||
you read a sequence of midi messages from a file or stream. | |||||
@param data the data to read from | |||||
@param maxBytesToUse the maximum number of bytes it's allowed to read | |||||
@param numBytesUsed returns the number of bytes that were actually needed | |||||
@param lastStatusByte in a sequence of midi messages, the initial byte | |||||
can be dropped from a message if it's the same as the | |||||
first byte of the previous message, so this lets you | |||||
supply the byte to use if the first byte of the message | |||||
has in fact been dropped. | |||||
@param timeStamp the time to give the midi message - this value doesn't | |||||
use any particular units, so will be application-specific | |||||
*/ | |||||
MidiMessage (const void* data, int maxBytesToUse, | |||||
int& numBytesUsed, uint8 lastStatusByte, | |||||
double timeStamp = 0); | |||||
/** Creates an active-sense message. | |||||
Since the MidiMessage has to contain a valid message, this default constructor | |||||
just initialises it with an empty sysex message. | |||||
*/ | |||||
MidiMessage() noexcept; | |||||
/** Creates a copy of another midi message. */ | |||||
MidiMessage (const MidiMessage& other); | |||||
/** Creates a copy of another midi message, with a different timestamp. */ | |||||
MidiMessage (const MidiMessage& other, double newTimeStamp); | |||||
/** Destructor. */ | |||||
~MidiMessage(); | |||||
/** Copies this message from another one. */ | |||||
MidiMessage& operator= (const MidiMessage& other); | |||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||||
MidiMessage (MidiMessage&& other) noexcept; | |||||
MidiMessage& operator= (MidiMessage&& other) noexcept; | |||||
#endif | |||||
//============================================================================== | |||||
/** Returns a pointer to the raw midi data. | |||||
@see getRawDataSize | |||||
*/ | |||||
uint8* getRawData() const noexcept { return data; } | |||||
/** Returns the number of bytes of data in the message. | |||||
@see getRawData | |||||
*/ | |||||
int getRawDataSize() const noexcept { return size; } | |||||
//============================================================================== | |||||
/** Returns the timestamp associated with this message. | |||||
The exact meaning of this time and its units will vary, as messages are used in | |||||
a variety of different contexts. | |||||
If you're getting the message from a midi file, this could be a time in seconds, or | |||||
a number of ticks - see MidiFile::convertTimestampTicksToSeconds(). | |||||
If the message is being used in a MidiBuffer, it might indicate the number of | |||||
audio samples from the start of the buffer. | |||||
If the message was created by a MidiInput, see MidiInputCallback::handleIncomingMidiMessage() | |||||
for details of the way that it initialises this value. | |||||
@see setTimeStamp, addToTimeStamp | |||||
*/ | |||||
double getTimeStamp() const noexcept { return timeStamp; } | |||||
/** Changes the message's associated timestamp. | |||||
The units for the timestamp will be application-specific - see the notes for getTimeStamp(). | |||||
@see addToTimeStamp, getTimeStamp | |||||
*/ | |||||
void setTimeStamp (double newTimestamp) noexcept { timeStamp = newTimestamp; } | |||||
/** Adds a value to the message's timestamp. | |||||
The units for the timestamp will be application-specific. | |||||
*/ | |||||
void addToTimeStamp (double delta) noexcept { timeStamp += delta; } | |||||
//============================================================================== | |||||
/** Returns the midi channel associated with the message. | |||||
@returns a value 1 to 16 if the message has a channel, or 0 if it hasn't (e.g. | |||||
if it's a sysex) | |||||
@see isForChannel, setChannel | |||||
*/ | |||||
int getChannel() const noexcept; | |||||
/** Returns true if the message applies to the given midi channel. | |||||
@param channelNumber the channel number to look for, in the range 1 to 16 | |||||
@see getChannel, setChannel | |||||
*/ | |||||
bool isForChannel (int channelNumber) const noexcept; | |||||
/** Changes the message's midi channel. | |||||
This won't do anything for non-channel messages like sysexes. | |||||
@param newChannelNumber the channel number to change it to, in the range 1 to 16 | |||||
*/ | |||||
void setChannel (int newChannelNumber) noexcept; | |||||
//============================================================================== | |||||
/** Returns true if this is a system-exclusive message. | |||||
*/ | |||||
bool isSysEx() const noexcept; | |||||
/** Returns a pointer to the sysex data inside the message. | |||||
If this event isn't a sysex event, it'll return 0. | |||||
@see getSysExDataSize | |||||
*/ | |||||
const uint8* getSysExData() const noexcept; | |||||
/** Returns the size of the sysex data. | |||||
This value excludes the 0xf0 header byte and the 0xf7 at the end. | |||||
@see getSysExData | |||||
*/ | |||||
int getSysExDataSize() const noexcept; | |||||
//============================================================================== | |||||
/** Returns true if this message is a 'key-down' event. | |||||
@param returnTrueForVelocity0 if true, then if this event is a note-on with | |||||
velocity 0, it will still be considered to be a note-on and the | |||||
method will return true. If returnTrueForVelocity0 is false, then | |||||
if this is a note-on event with velocity 0, it'll be regarded as | |||||
a note-off, and the method will return false | |||||
@see isNoteOff, getNoteNumber, getVelocity, noteOn | |||||
*/ | |||||
bool isNoteOn (bool returnTrueForVelocity0 = false) const noexcept; | |||||
/** Creates a key-down message (using a floating-point velocity). | |||||
@param channel the midi channel, in the range 1 to 16 | |||||
@param noteNumber the key number, 0 to 127 | |||||
@param velocity in the range 0 to 1.0 | |||||
@see isNoteOn | |||||
*/ | |||||
static MidiMessage noteOn (int channel, int noteNumber, float velocity) noexcept; | |||||
/** Creates a key-down message (using an integer velocity). | |||||
@param channel the midi channel, in the range 1 to 16 | |||||
@param noteNumber the key number, 0 to 127 | |||||
@param velocity in the range 0 to 127 | |||||
@see isNoteOn | |||||
*/ | |||||
static MidiMessage noteOn (int channel, int noteNumber, uint8 velocity) noexcept; | |||||
/** Returns true if this message is a 'key-up' event. | |||||
If returnTrueForNoteOnVelocity0 is true, then his will also return true | |||||
for a note-on event with a velocity of 0. | |||||
@see isNoteOn, getNoteNumber, getVelocity, noteOff | |||||
*/ | |||||
bool isNoteOff (bool returnTrueForNoteOnVelocity0 = true) const noexcept; | |||||
/** Creates a key-up message. | |||||
@param channel the midi channel, in the range 1 to 16 | |||||
@param noteNumber the key number, 0 to 127 | |||||
@param velocity in the range 0 to 127 | |||||
@see isNoteOff | |||||
*/ | |||||
static MidiMessage noteOff (int channel, int noteNumber, uint8 velocity = 0) noexcept; | |||||
/** Returns true if this message is a 'key-down' or 'key-up' event. | |||||
@see isNoteOn, isNoteOff | |||||
*/ | |||||
bool isNoteOnOrOff() const noexcept; | |||||
/** Returns the midi note number for note-on and note-off messages. | |||||
If the message isn't a note-on or off, the value returned is undefined. | |||||
@see isNoteOff, getMidiNoteName, getMidiNoteInHertz, setNoteNumber | |||||
*/ | |||||
int getNoteNumber() const noexcept; | |||||
/** Changes the midi note number of a note-on or note-off message. | |||||
If the message isn't a note on or off, this will do nothing. | |||||
*/ | |||||
void setNoteNumber (int newNoteNumber) noexcept; | |||||
//============================================================================== | |||||
/** Returns the velocity of a note-on or note-off message. | |||||
The value returned will be in the range 0 to 127. | |||||
If the message isn't a note-on or off event, it will return 0. | |||||
@see getFloatVelocity | |||||
*/ | |||||
uint8 getVelocity() const noexcept; | |||||
/** Returns the velocity of a note-on or note-off message. | |||||
The value returned will be in the range 0 to 1.0 | |||||
If the message isn't a note-on or off event, it will return 0. | |||||
@see getVelocity, setVelocity | |||||
*/ | |||||
float getFloatVelocity() const noexcept; | |||||
/** Changes the velocity of a note-on or note-off message. | |||||
If the message isn't a note on or off, this will do nothing. | |||||
@param newVelocity the new velocity, in the range 0 to 1.0 | |||||
@see getFloatVelocity, multiplyVelocity | |||||
*/ | |||||
void setVelocity (float newVelocity) noexcept; | |||||
/** Multiplies the velocity of a note-on or note-off message by a given amount. | |||||
If the message isn't a note on or off, this will do nothing. | |||||
@param scaleFactor the value by which to multiply the velocity | |||||
@see setVelocity | |||||
*/ | |||||
void multiplyVelocity (float scaleFactor) noexcept; | |||||
//============================================================================== | |||||
/** Returns true if this message is a 'sustain pedal down' controller message. */ | |||||
bool isSustainPedalOn() const noexcept; | |||||
/** Returns true if this message is a 'sustain pedal up' controller message. */ | |||||
bool isSustainPedalOff() const noexcept; | |||||
/** Returns true if this message is a 'sostenuto pedal down' controller message. */ | |||||
bool isSostenutoPedalOn() const noexcept; | |||||
/** Returns true if this message is a 'sostenuto pedal up' controller message. */ | |||||
bool isSostenutoPedalOff() const noexcept; | |||||
/** Returns true if this message is a 'soft pedal down' controller message. */ | |||||
bool isSoftPedalOn() const noexcept; | |||||
/** Returns true if this message is a 'soft pedal up' controller message. */ | |||||
bool isSoftPedalOff() const noexcept; | |||||
//============================================================================== | |||||
/** Returns true if the message is a program (patch) change message. | |||||
@see getProgramChangeNumber, getGMInstrumentName | |||||
*/ | |||||
bool isProgramChange() const noexcept; | |||||
/** Returns the new program number of a program change message. | |||||
If the message isn't a program change, the value returned will be | |||||
nonsense. | |||||
@see isProgramChange, getGMInstrumentName | |||||
*/ | |||||
int getProgramChangeNumber() const noexcept; | |||||
/** Creates a program-change message. | |||||
@param channel the midi channel, in the range 1 to 16 | |||||
@param programNumber the midi program number, 0 to 127 | |||||
@see isProgramChange, getGMInstrumentName | |||||
*/ | |||||
static MidiMessage programChange (int channel, int programNumber) noexcept; | |||||
//============================================================================== | |||||
/** Returns true if the message is a pitch-wheel move. | |||||
@see getPitchWheelValue, pitchWheel | |||||
*/ | |||||
bool isPitchWheel() const noexcept; | |||||
/** Returns the pitch wheel position from a pitch-wheel move message. | |||||
The value returned is a 14-bit number from 0 to 0x3fff, indicating the wheel position. | |||||
If called for messages which aren't pitch wheel events, the number returned will be | |||||
nonsense. | |||||
@see isPitchWheel | |||||
*/ | |||||
int getPitchWheelValue() const noexcept; | |||||
/** Creates a pitch-wheel move message. | |||||
@param channel the midi channel, in the range 1 to 16 | |||||
@param position the wheel position, in the range 0 to 16383 | |||||
@see isPitchWheel | |||||
*/ | |||||
static MidiMessage pitchWheel (int channel, int position) noexcept; | |||||
//============================================================================== | |||||
/** Returns true if the message is an aftertouch event. | |||||
For aftertouch events, use the getNoteNumber() method to find out the key | |||||
that it applies to, and getAftertouchValue() to find out the amount. Use | |||||
getChannel() to find out the channel. | |||||
@see getAftertouchValue, getNoteNumber | |||||
*/ | |||||
bool isAftertouch() const noexcept; | |||||
/** Returns the amount of aftertouch from an aftertouch messages. | |||||
The value returned is in the range 0 to 127, and will be nonsense for messages | |||||
other than aftertouch messages. | |||||
@see isAftertouch | |||||
*/ | |||||
int getAfterTouchValue() const noexcept; | |||||
/** Creates an aftertouch message. | |||||
@param channel the midi channel, in the range 1 to 16 | |||||
@param noteNumber the key number, 0 to 127 | |||||
@param aftertouchAmount the amount of aftertouch, 0 to 127 | |||||
@see isAftertouch | |||||
*/ | |||||
static MidiMessage aftertouchChange (int channel, | |||||
int noteNumber, | |||||
int aftertouchAmount) noexcept; | |||||
/** Returns true if the message is a channel-pressure change event. | |||||
This is like aftertouch, but common to the whole channel rather than a specific | |||||
note. Use getChannelPressureValue() to find out the pressure, and getChannel() | |||||
to find out the channel. | |||||
@see channelPressureChange | |||||
*/ | |||||
bool isChannelPressure() const noexcept; | |||||
/** Returns the pressure from a channel pressure change message. | |||||
@returns the pressure, in the range 0 to 127 | |||||
@see isChannelPressure, channelPressureChange | |||||
*/ | |||||
int getChannelPressureValue() const noexcept; | |||||
/** Creates a channel-pressure change event. | |||||
@param channel the midi channel: 1 to 16 | |||||
@param pressure the pressure, 0 to 127 | |||||
@see isChannelPressure | |||||
*/ | |||||
static MidiMessage channelPressureChange (int channel, int pressure) noexcept; | |||||
//============================================================================== | |||||
/** Returns true if this is a midi controller message. | |||||
@see getControllerNumber, getControllerValue, controllerEvent | |||||
*/ | |||||
bool isController() const noexcept; | |||||
/** Returns the controller number of a controller message. | |||||
The name of the controller can be looked up using the getControllerName() method. | |||||
Note that the value returned is invalid for messages that aren't controller changes. | |||||
@see isController, getControllerName, getControllerValue | |||||
*/ | |||||
int getControllerNumber() const noexcept; | |||||
/** Returns the controller value from a controller message. | |||||
A value 0 to 127 is returned to indicate the new controller position. | |||||
Note that the value returned is invalid for messages that aren't controller changes. | |||||
@see isController, getControllerNumber | |||||
*/ | |||||
int getControllerValue() const noexcept; | |||||
/** Returns true if this message is a controller message and if it has the specified | |||||
controller type. | |||||
*/ | |||||
bool isControllerOfType (int controllerType) const noexcept; | |||||
/** Creates a controller message. | |||||
@param channel the midi channel, in the range 1 to 16 | |||||
@param controllerType the type of controller | |||||
@param value the controller value | |||||
@see isController | |||||
*/ | |||||
static MidiMessage controllerEvent (int channel, | |||||
int controllerType, | |||||
int value) noexcept; | |||||
/** Checks whether this message is an all-notes-off message. | |||||
@see allNotesOff | |||||
*/ | |||||
bool isAllNotesOff() const noexcept; | |||||
/** Checks whether this message is an all-sound-off message. | |||||
@see allSoundOff | |||||
*/ | |||||
bool isAllSoundOff() const noexcept; | |||||
/** Creates an all-notes-off message. | |||||
@param channel the midi channel, in the range 1 to 16 | |||||
@see isAllNotesOff | |||||
*/ | |||||
static MidiMessage allNotesOff (int channel) noexcept; | |||||
/** Creates an all-sound-off message. | |||||
@param channel the midi channel, in the range 1 to 16 | |||||
@see isAllSoundOff | |||||
*/ | |||||
static MidiMessage allSoundOff (int channel) noexcept; | |||||
/** Creates an all-controllers-off message. | |||||
@param channel the midi channel, in the range 1 to 16 | |||||
*/ | |||||
static MidiMessage allControllersOff (int channel) noexcept; | |||||
//============================================================================== | |||||
/** Returns true if this event is a meta-event. | |||||
Meta-events are things like tempo changes, track names, etc. | |||||
@see getMetaEventType, isTrackMetaEvent, isEndOfTrackMetaEvent, | |||||
isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent, | |||||
isKeySignatureMetaEvent, isMidiChannelMetaEvent | |||||
*/ | |||||
bool isMetaEvent() const noexcept; | |||||
/** Returns a meta-event's type number. | |||||
If the message isn't a meta-event, this will return -1. | |||||
@see isMetaEvent, isTrackMetaEvent, isEndOfTrackMetaEvent, | |||||
isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent, | |||||
isKeySignatureMetaEvent, isMidiChannelMetaEvent | |||||
*/ | |||||
int getMetaEventType() const noexcept; | |||||
/** Returns a pointer to the data in a meta-event. | |||||
@see isMetaEvent, getMetaEventLength | |||||
*/ | |||||
const uint8* getMetaEventData() const noexcept; | |||||
/** Returns the length of the data for a meta-event. | |||||
@see isMetaEvent, getMetaEventData | |||||
*/ | |||||
int getMetaEventLength() const noexcept; | |||||
//============================================================================== | |||||
/** Returns true if this is a 'track' meta-event. */ | |||||
bool isTrackMetaEvent() const noexcept; | |||||
/** Returns true if this is an 'end-of-track' meta-event. */ | |||||
bool isEndOfTrackMetaEvent() const noexcept; | |||||
/** Creates an end-of-track meta-event. | |||||
@see isEndOfTrackMetaEvent | |||||
*/ | |||||
static MidiMessage endOfTrack() noexcept; | |||||
/** Returns true if this is an 'track name' meta-event. | |||||
You can use the getTextFromTextMetaEvent() method to get the track's name. | |||||
*/ | |||||
bool isTrackNameEvent() const noexcept; | |||||
/** Returns true if this is a 'text' meta-event. | |||||
@see getTextFromTextMetaEvent | |||||
*/ | |||||
bool isTextMetaEvent() const noexcept; | |||||
/** Returns the text from a text meta-event. | |||||
@see isTextMetaEvent | |||||
*/ | |||||
String getTextFromTextMetaEvent() const; | |||||
//============================================================================== | |||||
/** Returns true if this is a 'tempo' meta-event. | |||||
@see getTempoMetaEventTickLength, getTempoSecondsPerQuarterNote | |||||
*/ | |||||
bool isTempoMetaEvent() const noexcept; | |||||
/** Returns the tick length from a tempo meta-event. | |||||
@param timeFormat the 16-bit time format value from the midi file's header. | |||||
@returns the tick length (in seconds). | |||||
@see isTempoMetaEvent | |||||
*/ | |||||
double getTempoMetaEventTickLength (short timeFormat) const noexcept; | |||||
/** Calculates the seconds-per-quarter-note from a tempo meta-event. | |||||
@see isTempoMetaEvent, getTempoMetaEventTickLength | |||||
*/ | |||||
double getTempoSecondsPerQuarterNote() const noexcept; | |||||
/** Creates a tempo meta-event. | |||||
@see isTempoMetaEvent | |||||
*/ | |||||
static MidiMessage tempoMetaEvent (int microsecondsPerQuarterNote) noexcept; | |||||
//============================================================================== | |||||
/** Returns true if this is a 'time-signature' meta-event. | |||||
@see getTimeSignatureInfo | |||||
*/ | |||||
bool isTimeSignatureMetaEvent() const noexcept; | |||||
/** Returns the time-signature values from a time-signature meta-event. | |||||
@see isTimeSignatureMetaEvent | |||||
*/ | |||||
void getTimeSignatureInfo (int& numerator, int& denominator) const noexcept; | |||||
/** Creates a time-signature meta-event. | |||||
@see isTimeSignatureMetaEvent | |||||
*/ | |||||
static MidiMessage timeSignatureMetaEvent (int numerator, int denominator); | |||||
//============================================================================== | |||||
/** Returns true if this is a 'key-signature' meta-event. | |||||
@see getKeySignatureNumberOfSharpsOrFlats | |||||
*/ | |||||
bool isKeySignatureMetaEvent() const noexcept; | |||||
/** Returns the key from a key-signature meta-event. | |||||
@see isKeySignatureMetaEvent | |||||
*/ | |||||
int getKeySignatureNumberOfSharpsOrFlats() const noexcept; | |||||
//============================================================================== | |||||
/** Returns true if this is a 'channel' meta-event. | |||||
A channel meta-event specifies the midi channel that should be used | |||||
for subsequent meta-events. | |||||
@see getMidiChannelMetaEventChannel | |||||
*/ | |||||
bool isMidiChannelMetaEvent() const noexcept; | |||||
/** Returns the channel number from a channel meta-event. | |||||
@returns the channel, in the range 1 to 16. | |||||
@see isMidiChannelMetaEvent | |||||
*/ | |||||
int getMidiChannelMetaEventChannel() const noexcept; | |||||
/** Creates a midi channel meta-event. | |||||
@param channel the midi channel, in the range 1 to 16 | |||||
@see isMidiChannelMetaEvent | |||||
*/ | |||||
static MidiMessage midiChannelMetaEvent (int channel) noexcept; | |||||
//============================================================================== | |||||
/** Returns true if this is an active-sense message. */ | |||||
bool isActiveSense() const noexcept; | |||||
//============================================================================== | |||||
/** Returns true if this is a midi start event. | |||||
@see midiStart | |||||
*/ | |||||
bool isMidiStart() const noexcept; | |||||
/** Creates a midi start event. */ | |||||
static MidiMessage midiStart() noexcept; | |||||
/** Returns true if this is a midi continue event. | |||||
@see midiContinue | |||||
*/ | |||||
bool isMidiContinue() const noexcept; | |||||
/** Creates a midi continue event. */ | |||||
static MidiMessage midiContinue() noexcept; | |||||
/** Returns true if this is a midi stop event. | |||||
@see midiStop | |||||
*/ | |||||
bool isMidiStop() const noexcept; | |||||
/** Creates a midi stop event. */ | |||||
static MidiMessage midiStop() noexcept; | |||||
/** Returns true if this is a midi clock event. | |||||
@see midiClock, songPositionPointer | |||||
*/ | |||||
bool isMidiClock() const noexcept; | |||||
/** Creates a midi clock event. */ | |||||
static MidiMessage midiClock() noexcept; | |||||
/** Returns true if this is a song-position-pointer message. | |||||
@see getSongPositionPointerMidiBeat, songPositionPointer | |||||
*/ | |||||
bool isSongPositionPointer() const noexcept; | |||||
/** Returns the midi beat-number of a song-position-pointer message. | |||||
@see isSongPositionPointer, songPositionPointer | |||||
*/ | |||||
int getSongPositionPointerMidiBeat() const noexcept; | |||||
/** Creates a song-position-pointer message. | |||||
The position is a number of midi beats from the start of the song, where 1 midi | |||||
beat is 6 midi clocks, and there are 24 midi clocks in a quarter-note. So there | |||||
are 4 midi beats in a quarter-note. | |||||
@see isSongPositionPointer, getSongPositionPointerMidiBeat | |||||
*/ | |||||
static MidiMessage songPositionPointer (int positionInMidiBeats) noexcept; | |||||
//============================================================================== | |||||
/** Returns true if this is a quarter-frame midi timecode message. | |||||
@see quarterFrame, getQuarterFrameSequenceNumber, getQuarterFrameValue | |||||
*/ | |||||
bool isQuarterFrame() const noexcept; | |||||
/** Returns the sequence number of a quarter-frame midi timecode message. | |||||
This will be a value between 0 and 7. | |||||
@see isQuarterFrame, getQuarterFrameValue, quarterFrame | |||||
*/ | |||||
int getQuarterFrameSequenceNumber() const noexcept; | |||||
/** Returns the value from a quarter-frame message. | |||||
This will be the lower nybble of the message's data-byte, a value | |||||
between 0 and 15 | |||||
*/ | |||||
int getQuarterFrameValue() const noexcept; | |||||
/** Creates a quarter-frame MTC message. | |||||
@param sequenceNumber a value 0 to 7 for the upper nybble of the message's data byte | |||||
@param value a value 0 to 15 for the lower nybble of the message's data byte | |||||
*/ | |||||
static MidiMessage quarterFrame (int sequenceNumber, int value) noexcept; | |||||
/** SMPTE timecode types. | |||||
Used by the getFullFrameParameters() and fullFrame() methods. | |||||
*/ | |||||
enum SmpteTimecodeType | |||||
{ | |||||
fps24 = 0, | |||||
fps25 = 1, | |||||
fps30drop = 2, | |||||
fps30 = 3 | |||||
}; | |||||
/** Returns true if this is a full-frame midi timecode message. | |||||
*/ | |||||
bool isFullFrame() const noexcept; | |||||
/** Extracts the timecode information from a full-frame midi timecode message. | |||||
You should only call this on messages where you've used isFullFrame() to | |||||
check that they're the right kind. | |||||
*/ | |||||
void getFullFrameParameters (int& hours, | |||||
int& minutes, | |||||
int& seconds, | |||||
int& frames, | |||||
SmpteTimecodeType& timecodeType) const noexcept; | |||||
/** Creates a full-frame MTC message. | |||||
*/ | |||||
static MidiMessage fullFrame (int hours, | |||||
int minutes, | |||||
int seconds, | |||||
int frames, | |||||
SmpteTimecodeType timecodeType); | |||||
//============================================================================== | |||||
/** Types of MMC command. | |||||
@see isMidiMachineControlMessage, getMidiMachineControlCommand, midiMachineControlCommand | |||||
*/ | |||||
enum MidiMachineControlCommand | |||||
{ | |||||
mmc_stop = 1, | |||||
mmc_play = 2, | |||||
mmc_deferredplay = 3, | |||||
mmc_fastforward = 4, | |||||
mmc_rewind = 5, | |||||
mmc_recordStart = 6, | |||||
mmc_recordStop = 7, | |||||
mmc_pause = 9 | |||||
}; | |||||
/** Checks whether this is an MMC message. | |||||
If it is, you can use the getMidiMachineControlCommand() to find out its type. | |||||
*/ | |||||
bool isMidiMachineControlMessage() const noexcept; | |||||
/** For an MMC message, this returns its type. | |||||
Make sure it's actually an MMC message with isMidiMachineControlMessage() before | |||||
calling this method. | |||||
*/ | |||||
MidiMachineControlCommand getMidiMachineControlCommand() const noexcept; | |||||
/** Creates an MMC message. | |||||
*/ | |||||
static MidiMessage midiMachineControlCommand (MidiMachineControlCommand command); | |||||
/** Checks whether this is an MMC "goto" message. | |||||
If it is, the parameters passed-in are set to the time that the message contains. | |||||
@see midiMachineControlGoto | |||||
*/ | |||||
bool isMidiMachineControlGoto (int& hours, | |||||
int& minutes, | |||||
int& seconds, | |||||
int& frames) const noexcept; | |||||
/** Creates an MMC "goto" message. | |||||
This messages tells the device to go to a specific frame. | |||||
@see isMidiMachineControlGoto | |||||
*/ | |||||
static MidiMessage midiMachineControlGoto (int hours, | |||||
int minutes, | |||||
int seconds, | |||||
int frames); | |||||
//============================================================================== | |||||
/** Creates a master-volume change message. | |||||
@param volume the volume, 0 to 1.0 | |||||
*/ | |||||
static MidiMessage masterVolume (float volume); | |||||
//============================================================================== | |||||
/** Creates a system-exclusive message. | |||||
The data passed in is wrapped with header and tail bytes of 0xf0 and 0xf7. | |||||
*/ | |||||
static MidiMessage createSysExMessage (const uint8* sysexData, | |||||
int dataSize); | |||||
//============================================================================== | |||||
/** Reads a midi variable-length integer. | |||||
@param data the data to read the number from | |||||
@param numBytesUsed on return, this will be set to the number of bytes that were read | |||||
*/ | |||||
static int readVariableLengthVal (const uint8* data, | |||||
int& numBytesUsed) noexcept; | |||||
/** Based on the first byte of a short midi message, this uses a lookup table | |||||
to return the message length (either 1, 2, or 3 bytes). | |||||
The value passed in must be 0x80 or higher. | |||||
*/ | |||||
static int getMessageLengthFromFirstByte (const uint8 firstByte) noexcept; | |||||
//============================================================================== | |||||
/** Returns the name of a midi note number. | |||||
E.g "C", "D#", etc. | |||||
@param noteNumber the midi note number, 0 to 127 | |||||
@param useSharps if true, sharpened notes are used, e.g. "C#", otherwise | |||||
they'll be flattened, e.g. "Db" | |||||
@param includeOctaveNumber if true, the octave number will be appended to the string, | |||||
e.g. "C#4" | |||||
@param octaveNumForMiddleC if an octave number is being appended, this indicates the | |||||
number that will be used for middle C's octave | |||||
@see getMidiNoteInHertz | |||||
*/ | |||||
static String getMidiNoteName (int noteNumber, | |||||
bool useSharps, | |||||
bool includeOctaveNumber, | |||||
int octaveNumForMiddleC); | |||||
/** Returns the frequency of a midi note number. | |||||
The frequencyOfA parameter is an optional frequency for 'A', normally 440-444Hz for concert pitch. | |||||
@see getMidiNoteName | |||||
*/ | |||||
static double getMidiNoteInHertz (int noteNumber, const double frequencyOfA = 440.0) noexcept; | |||||
/** Returns the standard name of a GM instrument. | |||||
@param midiInstrumentNumber the program number 0 to 127 | |||||
@see getProgramChangeNumber | |||||
*/ | |||||
static String getGMInstrumentName (int midiInstrumentNumber); | |||||
/** Returns the name of a bank of GM instruments. | |||||
@param midiBankNumber the bank, 0 to 15 | |||||
*/ | |||||
static String getGMInstrumentBankName (int midiBankNumber); | |||||
/** Returns the standard name of a channel 10 percussion sound. | |||||
@param midiNoteNumber the key number, 35 to 81 | |||||
*/ | |||||
static String getRhythmInstrumentName (int midiNoteNumber); | |||||
/** Returns the name of a controller type number. | |||||
@see getControllerNumber | |||||
*/ | |||||
static String getControllerName (int controllerNumber); | |||||
private: | |||||
//============================================================================== | |||||
double timeStamp; | |||||
uint8* data; | |||||
int size; | |||||
#ifndef DOXYGEN | |||||
union | |||||
{ | |||||
uint8 asBytes[4]; | |||||
uint32 asInt32; | |||||
} preallocatedData; | |||||
#endif | |||||
void freeData() noexcept; | |||||
void setToUseInternalData() noexcept; | |||||
bool usesAllocatedData() const noexcept; | |||||
}; | |||||
#endif // __JUCE_MIDIMESSAGE_JUCEHEADER__ |
@@ -0,0 +1,335 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
MidiMessageSequence::MidiMessageSequence() | |||||
{ | |||||
} | |||||
MidiMessageSequence::MidiMessageSequence (const MidiMessageSequence& other) | |||||
{ | |||||
list.ensureStorageAllocated (other.list.size()); | |||||
for (int i = 0; i < other.list.size(); ++i) | |||||
list.add (new MidiEventHolder (other.list.getUnchecked(i)->message)); | |||||
} | |||||
MidiMessageSequence& MidiMessageSequence::operator= (const MidiMessageSequence& other) | |||||
{ | |||||
MidiMessageSequence otherCopy (other); | |||||
swapWith (otherCopy); | |||||
return *this; | |||||
} | |||||
void MidiMessageSequence::swapWith (MidiMessageSequence& other) noexcept | |||||
{ | |||||
list.swapWithArray (other.list); | |||||
} | |||||
MidiMessageSequence::~MidiMessageSequence() | |||||
{ | |||||
} | |||||
void MidiMessageSequence::clear() | |||||
{ | |||||
list.clear(); | |||||
} | |||||
int MidiMessageSequence::getNumEvents() const | |||||
{ | |||||
return list.size(); | |||||
} | |||||
MidiMessageSequence::MidiEventHolder* MidiMessageSequence::getEventPointer (const int index) const | |||||
{ | |||||
return list [index]; | |||||
} | |||||
double MidiMessageSequence::getTimeOfMatchingKeyUp (const int index) const | |||||
{ | |||||
if (const MidiEventHolder* const meh = list [index]) | |||||
if (meh->noteOffObject != nullptr) | |||||
return meh->noteOffObject->message.getTimeStamp(); | |||||
return 0.0; | |||||
} | |||||
int MidiMessageSequence::getIndexOfMatchingKeyUp (const int index) const | |||||
{ | |||||
if (const MidiEventHolder* const meh = list [index]) | |||||
return list.indexOf (meh->noteOffObject); | |||||
return -1; | |||||
} | |||||
int MidiMessageSequence::getIndexOf (MidiEventHolder* const event) const | |||||
{ | |||||
return list.indexOf (event); | |||||
} | |||||
int MidiMessageSequence::getNextIndexAtTime (const double timeStamp) const | |||||
{ | |||||
const int numEvents = list.size(); | |||||
int i; | |||||
for (i = 0; i < numEvents; ++i) | |||||
if (list.getUnchecked(i)->message.getTimeStamp() >= timeStamp) | |||||
break; | |||||
return i; | |||||
} | |||||
//============================================================================== | |||||
double MidiMessageSequence::getStartTime() const | |||||
{ | |||||
return getEventTime (0); | |||||
} | |||||
double MidiMessageSequence::getEndTime() const | |||||
{ | |||||
return getEventTime (list.size() - 1); | |||||
} | |||||
double MidiMessageSequence::getEventTime (const int index) const | |||||
{ | |||||
if (const MidiEventHolder* const meh = list [index]) | |||||
return meh->message.getTimeStamp(); | |||||
return 0.0; | |||||
} | |||||
//============================================================================== | |||||
MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (const MidiMessage& newMessage, | |||||
double timeAdjustment) | |||||
{ | |||||
MidiEventHolder* const newOne = new MidiEventHolder (newMessage); | |||||
timeAdjustment += newMessage.getTimeStamp(); | |||||
newOne->message.setTimeStamp (timeAdjustment); | |||||
int i; | |||||
for (i = list.size(); --i >= 0;) | |||||
if (list.getUnchecked(i)->message.getTimeStamp() <= timeAdjustment) | |||||
break; | |||||
list.insert (i + 1, newOne); | |||||
return newOne; | |||||
} | |||||
void MidiMessageSequence::deleteEvent (const int index, | |||||
const bool deleteMatchingNoteUp) | |||||
{ | |||||
if (isPositiveAndBelow (index, list.size())) | |||||
{ | |||||
if (deleteMatchingNoteUp) | |||||
deleteEvent (getIndexOfMatchingKeyUp (index), false); | |||||
list.remove (index); | |||||
} | |||||
} | |||||
struct MidiMessageSequenceSorter | |||||
{ | |||||
static int compareElements (const MidiMessageSequence::MidiEventHolder* const first, | |||||
const MidiMessageSequence::MidiEventHolder* const second) noexcept | |||||
{ | |||||
const double diff = first->message.getTimeStamp() - second->message.getTimeStamp(); | |||||
return (diff > 0) - (diff < 0); | |||||
} | |||||
}; | |||||
void MidiMessageSequence::addSequence (const MidiMessageSequence& other, | |||||
double timeAdjustment, | |||||
double firstAllowableTime, | |||||
double endOfAllowableDestTimes) | |||||
{ | |||||
firstAllowableTime -= timeAdjustment; | |||||
endOfAllowableDestTimes -= timeAdjustment; | |||||
for (int i = 0; i < other.list.size(); ++i) | |||||
{ | |||||
const MidiMessage& m = other.list.getUnchecked(i)->message; | |||||
const double t = m.getTimeStamp(); | |||||
if (t >= firstAllowableTime && t < endOfAllowableDestTimes) | |||||
{ | |||||
MidiEventHolder* const newOne = new MidiEventHolder (m); | |||||
newOne->message.setTimeStamp (timeAdjustment + t); | |||||
list.add (newOne); | |||||
} | |||||
} | |||||
sort(); | |||||
} | |||||
//============================================================================== | |||||
void MidiMessageSequence::sort() | |||||
{ | |||||
MidiMessageSequenceSorter sorter; | |||||
list.sort (sorter, true); | |||||
} | |||||
void MidiMessageSequence::updateMatchedPairs() | |||||
{ | |||||
for (int i = 0; i < list.size(); ++i) | |||||
{ | |||||
MidiEventHolder* const meh = list.getUnchecked(i); | |||||
const MidiMessage& m1 = meh->message; | |||||
if (m1.isNoteOn()) | |||||
{ | |||||
meh->noteOffObject = nullptr; | |||||
const int note = m1.getNoteNumber(); | |||||
const int chan = m1.getChannel(); | |||||
const int len = list.size(); | |||||
for (int j = i + 1; j < len; ++j) | |||||
{ | |||||
const MidiMessage& m = list.getUnchecked(j)->message; | |||||
if (m.getNoteNumber() == note && m.getChannel() == chan) | |||||
{ | |||||
if (m.isNoteOff()) | |||||
{ | |||||
meh->noteOffObject = list[j]; | |||||
break; | |||||
} | |||||
else if (m.isNoteOn()) | |||||
{ | |||||
MidiEventHolder* const newEvent = new MidiEventHolder (MidiMessage::noteOff (chan, note)); | |||||
list.insert (j, newEvent); | |||||
newEvent->message.setTimeStamp (m.getTimeStamp()); | |||||
meh->noteOffObject = newEvent; | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
void MidiMessageSequence::addTimeToMessages (const double delta) | |||||
{ | |||||
for (int i = list.size(); --i >= 0;) | |||||
{ | |||||
MidiMessage& mm = list.getUnchecked(i)->message; | |||||
mm.setTimeStamp (mm.getTimeStamp() + delta); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void MidiMessageSequence::extractMidiChannelMessages (const int channelNumberToExtract, | |||||
MidiMessageSequence& destSequence, | |||||
const bool alsoIncludeMetaEvents) const | |||||
{ | |||||
for (int i = 0; i < list.size(); ++i) | |||||
{ | |||||
const MidiMessage& mm = list.getUnchecked(i)->message; | |||||
if (mm.isForChannel (channelNumberToExtract) || (alsoIncludeMetaEvents && mm.isMetaEvent())) | |||||
destSequence.addEvent (mm); | |||||
} | |||||
} | |||||
void MidiMessageSequence::extractSysExMessages (MidiMessageSequence& destSequence) const | |||||
{ | |||||
for (int i = 0; i < list.size(); ++i) | |||||
{ | |||||
const MidiMessage& mm = list.getUnchecked(i)->message; | |||||
if (mm.isSysEx()) | |||||
destSequence.addEvent (mm); | |||||
} | |||||
} | |||||
void MidiMessageSequence::deleteMidiChannelMessages (const int channelNumberToRemove) | |||||
{ | |||||
for (int i = list.size(); --i >= 0;) | |||||
if (list.getUnchecked(i)->message.isForChannel (channelNumberToRemove)) | |||||
list.remove(i); | |||||
} | |||||
void MidiMessageSequence::deleteSysExMessages() | |||||
{ | |||||
for (int i = list.size(); --i >= 0;) | |||||
if (list.getUnchecked(i)->message.isSysEx()) | |||||
list.remove(i); | |||||
} | |||||
//============================================================================== | |||||
void MidiMessageSequence::createControllerUpdatesForTime (const int channelNumber, | |||||
const double time, | |||||
OwnedArray<MidiMessage>& dest) | |||||
{ | |||||
bool doneProg = false; | |||||
bool donePitchWheel = false; | |||||
Array <int> doneControllers; | |||||
doneControllers.ensureStorageAllocated (32); | |||||
for (int i = list.size(); --i >= 0;) | |||||
{ | |||||
const MidiMessage& mm = list.getUnchecked(i)->message; | |||||
if (mm.isForChannel (channelNumber) && mm.getTimeStamp() <= time) | |||||
{ | |||||
if (mm.isProgramChange()) | |||||
{ | |||||
if (! doneProg) | |||||
{ | |||||
dest.add (new MidiMessage (mm, 0.0)); | |||||
doneProg = true; | |||||
} | |||||
} | |||||
else if (mm.isController()) | |||||
{ | |||||
if (! doneControllers.contains (mm.getControllerNumber())) | |||||
{ | |||||
dest.add (new MidiMessage (mm, 0.0)); | |||||
doneControllers.add (mm.getControllerNumber()); | |||||
} | |||||
} | |||||
else if (mm.isPitchWheel()) | |||||
{ | |||||
if (! donePitchWheel) | |||||
{ | |||||
dest.add (new MidiMessage (mm, 0.0)); | |||||
donePitchWheel = true; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
MidiMessageSequence::MidiEventHolder::MidiEventHolder (const MidiMessage& mm) | |||||
: message (mm), | |||||
noteOffObject (nullptr) | |||||
{ | |||||
} | |||||
MidiMessageSequence::MidiEventHolder::~MidiEventHolder() | |||||
{ | |||||
} |
@@ -0,0 +1,281 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_MIDIMESSAGESEQUENCE_JUCEHEADER__ | |||||
#define __JUCE_MIDIMESSAGESEQUENCE_JUCEHEADER__ | |||||
#include "juce_MidiMessage.h" | |||||
//============================================================================== | |||||
/** | |||||
A sequence of timestamped midi messages. | |||||
This allows the sequence to be manipulated, and also to be read from and | |||||
written to a standard midi file. | |||||
@see MidiMessage, MidiFile | |||||
*/ | |||||
class JUCE_API MidiMessageSequence | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an empty midi sequence object. */ | |||||
MidiMessageSequence(); | |||||
/** Creates a copy of another sequence. */ | |||||
MidiMessageSequence (const MidiMessageSequence& other); | |||||
/** Replaces this sequence with another one. */ | |||||
MidiMessageSequence& operator= (const MidiMessageSequence& other); | |||||
/** Destructor. */ | |||||
~MidiMessageSequence(); | |||||
//============================================================================== | |||||
/** Structure used to hold midi events in the sequence. | |||||
These structures act as 'handles' on the events as they are moved about in | |||||
the list, and make it quick to find the matching note-offs for note-on events. | |||||
@see MidiMessageSequence::getEventPointer | |||||
*/ | |||||
class MidiEventHolder | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Destructor. */ | |||||
~MidiEventHolder(); | |||||
/** The message itself, whose timestamp is used to specify the event's time. | |||||
*/ | |||||
MidiMessage message; | |||||
/** The matching note-off event (if this is a note-on event). | |||||
If this isn't a note-on, this pointer will be null. | |||||
Use the MidiMessageSequence::updateMatchedPairs() method to keep these | |||||
note-offs up-to-date after events have been moved around in the sequence | |||||
or deleted. | |||||
*/ | |||||
MidiEventHolder* noteOffObject; | |||||
private: | |||||
//============================================================================== | |||||
friend class MidiMessageSequence; | |||||
MidiEventHolder (const MidiMessage& message); | |||||
JUCE_LEAK_DETECTOR (MidiEventHolder) | |||||
}; | |||||
//============================================================================== | |||||
/** Clears the sequence. */ | |||||
void clear(); | |||||
/** Returns the number of events in the sequence. */ | |||||
int getNumEvents() const; | |||||
/** Returns a pointer to one of the events. */ | |||||
MidiEventHolder* getEventPointer (int index) const; | |||||
/** Returns the time of the note-up that matches the note-on at this index. | |||||
If the event at this index isn't a note-on, it'll just return 0. | |||||
@see MidiMessageSequence::MidiEventHolder::noteOffObject | |||||
*/ | |||||
double getTimeOfMatchingKeyUp (int index) const; | |||||
/** Returns the index of the note-up that matches the note-on at this index. | |||||
If the event at this index isn't a note-on, it'll just return -1. | |||||
@see MidiMessageSequence::MidiEventHolder::noteOffObject | |||||
*/ | |||||
int getIndexOfMatchingKeyUp (int index) const; | |||||
/** Returns the index of an event. */ | |||||
int getIndexOf (MidiEventHolder* event) const; | |||||
/** Returns the index of the first event on or after the given timestamp. | |||||
If the time is beyond the end of the sequence, this will return the | |||||
number of events. | |||||
*/ | |||||
int getNextIndexAtTime (double timeStamp) const; | |||||
//============================================================================== | |||||
/** Returns the timestamp of the first event in the sequence. | |||||
@see getEndTime | |||||
*/ | |||||
double getStartTime() const; | |||||
/** Returns the timestamp of the last event in the sequence. | |||||
@see getStartTime | |||||
*/ | |||||
double getEndTime() const; | |||||
/** Returns the timestamp of the event at a given index. | |||||
If the index is out-of-range, this will return 0.0 | |||||
*/ | |||||
double getEventTime (int index) const; | |||||
//============================================================================== | |||||
/** Inserts a midi message into the sequence. | |||||
The index at which the new message gets inserted will depend on its timestamp, | |||||
because the sequence is kept sorted. | |||||
Remember to call updateMatchedPairs() after adding note-on events. | |||||
@param newMessage the new message to add (an internal copy will be made) | |||||
@param timeAdjustment an optional value to add to the timestamp of the message | |||||
that will be inserted | |||||
@see updateMatchedPairs | |||||
*/ | |||||
MidiEventHolder* addEvent (const MidiMessage& newMessage, | |||||
double timeAdjustment = 0); | |||||
/** Deletes one of the events in the sequence. | |||||
Remember to call updateMatchedPairs() after removing events. | |||||
@param index the index of the event to delete | |||||
@param deleteMatchingNoteUp whether to also remove the matching note-off | |||||
if the event you're removing is a note-on | |||||
*/ | |||||
void deleteEvent (int index, bool deleteMatchingNoteUp); | |||||
/** Merges another sequence into this one. | |||||
Remember to call updateMatchedPairs() after using this method. | |||||
@param other the sequence to add from | |||||
@param timeAdjustmentDelta an amount to add to the timestamps of the midi events | |||||
as they are read from the other sequence | |||||
@param firstAllowableDestTime events will not be added if their time is earlier | |||||
than this time. (This is after their time has been adjusted | |||||
by the timeAdjustmentDelta) | |||||
@param endOfAllowableDestTimes events will not be added if their time is equal to | |||||
or greater than this time. (This is after their time has | |||||
been adjusted by the timeAdjustmentDelta) | |||||
*/ | |||||
void addSequence (const MidiMessageSequence& other, | |||||
double timeAdjustmentDelta, | |||||
double firstAllowableDestTime, | |||||
double endOfAllowableDestTimes); | |||||
//============================================================================== | |||||
/** Makes sure all the note-on and note-off pairs are up-to-date. | |||||
Call this after moving messages about or deleting/adding messages, and it | |||||
will scan the list and make sure all the note-offs in the MidiEventHolder | |||||
structures are pointing at the correct ones. | |||||
*/ | |||||
void updateMatchedPairs(); | |||||
/** Forces a sort of the sequence. | |||||
You may need to call this if you've manually modified the timestamps of some | |||||
events such that the overall order now needs updating. | |||||
*/ | |||||
void sort(); | |||||
//============================================================================== | |||||
/** Copies all the messages for a particular midi channel to another sequence. | |||||
@param channelNumberToExtract the midi channel to look for, in the range 1 to 16 | |||||
@param destSequence the sequence that the chosen events should be copied to | |||||
@param alsoIncludeMetaEvents if true, any meta-events (which don't apply to a specific | |||||
channel) will also be copied across. | |||||
@see extractSysExMessages | |||||
*/ | |||||
void extractMidiChannelMessages (int channelNumberToExtract, | |||||
MidiMessageSequence& destSequence, | |||||
bool alsoIncludeMetaEvents) const; | |||||
/** Copies all midi sys-ex messages to another sequence. | |||||
@param destSequence this is the sequence to which any sys-exes in this sequence | |||||
will be added | |||||
@see extractMidiChannelMessages | |||||
*/ | |||||
void extractSysExMessages (MidiMessageSequence& destSequence) const; | |||||
/** Removes any messages in this sequence that have a specific midi channel. | |||||
@param channelNumberToRemove the midi channel to look for, in the range 1 to 16 | |||||
*/ | |||||
void deleteMidiChannelMessages (int channelNumberToRemove); | |||||
/** Removes any sys-ex messages from this sequence. | |||||
*/ | |||||
void deleteSysExMessages(); | |||||
/** Adds an offset to the timestamps of all events in the sequence. | |||||
@param deltaTime the amount to add to each timestamp. | |||||
*/ | |||||
void addTimeToMessages (double deltaTime); | |||||
//============================================================================== | |||||
/** Scans through the sequence to determine the state of any midi controllers at | |||||
a given time. | |||||
This will create a sequence of midi controller changes that can be | |||||
used to set all midi controllers to the state they would be in at the | |||||
specified time within this sequence. | |||||
As well as controllers, it will also recreate the midi program number | |||||
and pitch bend position. | |||||
@param channelNumber the midi channel to look for, in the range 1 to 16. Controllers | |||||
for other channels will be ignored. | |||||
@param time the time at which you want to find out the state - there are | |||||
no explicit units for this time measurement, it's the same units | |||||
as used for the timestamps of the messages | |||||
@param resultMessages an array to which midi controller-change messages will be added. This | |||||
will be the minimum number of controller changes to recreate the | |||||
state at the required time. | |||||
*/ | |||||
void createControllerUpdatesForTime (int channelNumber, double time, | |||||
OwnedArray<MidiMessage>& resultMessages); | |||||
//============================================================================== | |||||
/** Swaps this sequence with another one. */ | |||||
void swapWith (MidiMessageSequence& other) noexcept; | |||||
private: | |||||
//============================================================================== | |||||
friend class MidiFile; | |||||
OwnedArray <MidiEventHolder> list; | |||||
JUCE_LEAK_DETECTOR (MidiMessageSequence) | |||||
}; | |||||
#endif // __JUCE_MIDIMESSAGESEQUENCE_JUCEHEADER__ |
@@ -0,0 +1,184 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_AUDIOSOURCE_JUCEHEADER__ | |||||
#define __JUCE_AUDIOSOURCE_JUCEHEADER__ | |||||
#include "../buffers/juce_AudioSampleBuffer.h" | |||||
//============================================================================== | |||||
/** | |||||
Used by AudioSource::getNextAudioBlock(). | |||||
*/ | |||||
struct JUCE_API AudioSourceChannelInfo | |||||
{ | |||||
/** Creates an uninitialised AudioSourceChannelInfo. */ | |||||
AudioSourceChannelInfo() noexcept | |||||
{ | |||||
} | |||||
/** Creates an AudioSourceChannelInfo. */ | |||||
AudioSourceChannelInfo (AudioSampleBuffer* bufferToUse, | |||||
int startSampleOffset, int numSamplesToUse) noexcept | |||||
: buffer (bufferToUse), | |||||
startSample (startSampleOffset), | |||||
numSamples (numSamplesToUse) | |||||
{ | |||||
} | |||||
/** Creates an AudioSourceChannelInfo that uses the whole of a buffer. | |||||
Note that the buffer provided must not be deleted while the | |||||
AudioSourceChannelInfo is still using it. | |||||
*/ | |||||
explicit AudioSourceChannelInfo (AudioSampleBuffer& bufferToUse) noexcept | |||||
: buffer (&bufferToUse), | |||||
startSample (0), | |||||
numSamples (bufferToUse.getNumSamples()) | |||||
{ | |||||
} | |||||
/** The destination buffer to fill with audio data. | |||||
When the AudioSource::getNextAudioBlock() method is called, the active section | |||||
of this buffer should be filled with whatever output the source produces. | |||||
Only the samples specified by the startSample and numSamples members of this structure | |||||
should be affected by the call. | |||||
The contents of the buffer when it is passed to the the AudioSource::getNextAudioBlock() | |||||
method can be treated as the input if the source is performing some kind of filter operation, | |||||
but should be cleared if this is not the case - the clearActiveBufferRegion() is | |||||
a handy way of doing this. | |||||
The number of channels in the buffer could be anything, so the AudioSource | |||||
must cope with this in whatever way is appropriate for its function. | |||||
*/ | |||||
AudioSampleBuffer* buffer; | |||||
/** The first sample in the buffer from which the callback is expected | |||||
to write data. */ | |||||
int startSample; | |||||
/** The number of samples in the buffer which the callback is expected to | |||||
fill with data. */ | |||||
int numSamples; | |||||
/** Convenient method to clear the buffer if the source is not producing any data. */ | |||||
void clearActiveBufferRegion() const | |||||
{ | |||||
if (buffer != nullptr) | |||||
buffer->clear (startSample, numSamples); | |||||
} | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
Base class for objects that can produce a continuous stream of audio. | |||||
An AudioSource has two states: 'prepared' and 'unprepared'. | |||||
When a source needs to be played, it is first put into a 'prepared' state by a call to | |||||
prepareToPlay(), and then repeated calls will be made to its getNextAudioBlock() method to | |||||
process the audio data. | |||||
Once playback has finished, the releaseResources() method is called to put the stream | |||||
back into an 'unprepared' state. | |||||
@see AudioFormatReaderSource, ResamplingAudioSource | |||||
*/ | |||||
class JUCE_API AudioSource | |||||
{ | |||||
protected: | |||||
//============================================================================== | |||||
/** Creates an AudioSource. */ | |||||
AudioSource() noexcept {} | |||||
public: | |||||
/** Destructor. */ | |||||
virtual ~AudioSource() {} | |||||
//============================================================================== | |||||
/** Tells the source to prepare for playing. | |||||
An AudioSource has two states: prepared and unprepared. | |||||
The prepareToPlay() method is guaranteed to be called at least once on an 'unpreprared' | |||||
source to put it into a 'prepared' state before any calls will be made to getNextAudioBlock(). | |||||
This callback allows the source to initialise any resources it might need when playing. | |||||
Once playback has finished, the releaseResources() method is called to put the stream | |||||
back into an 'unprepared' state. | |||||
Note that this method could be called more than once in succession without | |||||
a matching call to releaseResources(), so make sure your code is robust and | |||||
can handle that kind of situation. | |||||
@param samplesPerBlockExpected the number of samples that the source | |||||
will be expected to supply each time its | |||||
getNextAudioBlock() method is called. This | |||||
number may vary slightly, because it will be dependent | |||||
on audio hardware callbacks, and these aren't | |||||
guaranteed to always use a constant block size, so | |||||
the source should be able to cope with small variations. | |||||
@param sampleRate the sample rate that the output will be used at - this | |||||
is needed by sources such as tone generators. | |||||
@see releaseResources, getNextAudioBlock | |||||
*/ | |||||
virtual void prepareToPlay (int samplesPerBlockExpected, | |||||
double sampleRate) = 0; | |||||
/** Allows the source to release anything it no longer needs after playback has stopped. | |||||
This will be called when the source is no longer going to have its getNextAudioBlock() | |||||
method called, so it should release any spare memory, etc. that it might have | |||||
allocated during the prepareToPlay() call. | |||||
Note that there's no guarantee that prepareToPlay() will actually have been called before | |||||
releaseResources(), and it may be called more than once in succession, so make sure your | |||||
code is robust and doesn't make any assumptions about when it will be called. | |||||
@see prepareToPlay, getNextAudioBlock | |||||
*/ | |||||
virtual void releaseResources() = 0; | |||||
/** Called repeatedly to fetch subsequent blocks of audio data. | |||||
After calling the prepareToPlay() method, this callback will be made each | |||||
time the audio playback hardware (or whatever other destination the audio | |||||
data is going to) needs another block of data. | |||||
It will generally be called on a high-priority system thread, or possibly even | |||||
an interrupt, so be careful not to do too much work here, as that will cause | |||||
audio glitches! | |||||
@see AudioSourceChannelInfo, prepareToPlay, releaseResources | |||||
*/ | |||||
virtual void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) = 0; | |||||
}; | |||||
#endif // __JUCE_AUDIOSOURCE_JUCEHEADER__ |
@@ -0,0 +1,262 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
BufferingAudioSource::BufferingAudioSource (PositionableAudioSource* source_, | |||||
TimeSliceThread& backgroundThread_, | |||||
const bool deleteSourceWhenDeleted, | |||||
const int numberOfSamplesToBuffer_, | |||||
const int numberOfChannels_) | |||||
: source (source_, deleteSourceWhenDeleted), | |||||
backgroundThread (backgroundThread_), | |||||
numberOfSamplesToBuffer (jmax (1024, numberOfSamplesToBuffer_)), | |||||
numberOfChannels (numberOfChannels_), | |||||
buffer (numberOfChannels_, 0), | |||||
bufferValidStart (0), | |||||
bufferValidEnd (0), | |||||
nextPlayPos (0), | |||||
wasSourceLooping (false), | |||||
isPrepared (false) | |||||
{ | |||||
jassert (source_ != nullptr); | |||||
jassert (numberOfSamplesToBuffer_ > 1024); // not much point using this class if you're | |||||
// not using a larger buffer.. | |||||
} | |||||
BufferingAudioSource::~BufferingAudioSource() | |||||
{ | |||||
releaseResources(); | |||||
} | |||||
//============================================================================== | |||||
void BufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate_) | |||||
{ | |||||
const int bufferSizeNeeded = jmax (samplesPerBlockExpected * 2, numberOfSamplesToBuffer); | |||||
if (sampleRate_ != sampleRate | |||||
|| bufferSizeNeeded != buffer.getNumSamples() | |||||
|| ! isPrepared) | |||||
{ | |||||
backgroundThread.removeTimeSliceClient (this); | |||||
isPrepared = true; | |||||
sampleRate = sampleRate_; | |||||
source->prepareToPlay (samplesPerBlockExpected, sampleRate_); | |||||
buffer.setSize (numberOfChannels, bufferSizeNeeded); | |||||
buffer.clear(); | |||||
bufferValidStart = 0; | |||||
bufferValidEnd = 0; | |||||
backgroundThread.addTimeSliceClient (this); | |||||
while (bufferValidEnd - bufferValidStart < jmin (((int) sampleRate_) / 4, | |||||
buffer.getNumSamples() / 2)) | |||||
{ | |||||
backgroundThread.moveToFrontOfQueue (this); | |||||
Thread::sleep (5); | |||||
} | |||||
} | |||||
} | |||||
void BufferingAudioSource::releaseResources() | |||||
{ | |||||
isPrepared = false; | |||||
backgroundThread.removeTimeSliceClient (this); | |||||
buffer.setSize (numberOfChannels, 0); | |||||
source->releaseResources(); | |||||
} | |||||
void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) | |||||
{ | |||||
const ScopedLock sl (bufferStartPosLock); | |||||
const int validStart = (int) (jlimit (bufferValidStart, bufferValidEnd, nextPlayPos) - nextPlayPos); | |||||
const int validEnd = (int) (jlimit (bufferValidStart, bufferValidEnd, nextPlayPos + info.numSamples) - nextPlayPos); | |||||
if (validStart == validEnd) | |||||
{ | |||||
// total cache miss | |||||
info.clearActiveBufferRegion(); | |||||
} | |||||
else | |||||
{ | |||||
if (validStart > 0) | |||||
info.buffer->clear (info.startSample, validStart); // partial cache miss at start | |||||
if (validEnd < info.numSamples) | |||||
info.buffer->clear (info.startSample + validEnd, | |||||
info.numSamples - validEnd); // partial cache miss at end | |||||
if (validStart < validEnd) | |||||
{ | |||||
for (int chan = jmin (numberOfChannels, info.buffer->getNumChannels()); --chan >= 0;) | |||||
{ | |||||
jassert (buffer.getNumSamples() > 0); | |||||
const int startBufferIndex = (int) ((validStart + nextPlayPos) % buffer.getNumSamples()); | |||||
const int endBufferIndex = (int) ((validEnd + nextPlayPos) % buffer.getNumSamples()); | |||||
if (startBufferIndex < endBufferIndex) | |||||
{ | |||||
info.buffer->copyFrom (chan, info.startSample + validStart, | |||||
buffer, | |||||
chan, startBufferIndex, | |||||
validEnd - validStart); | |||||
} | |||||
else | |||||
{ | |||||
const int initialSize = buffer.getNumSamples() - startBufferIndex; | |||||
info.buffer->copyFrom (chan, info.startSample + validStart, | |||||
buffer, | |||||
chan, startBufferIndex, | |||||
initialSize); | |||||
info.buffer->copyFrom (chan, info.startSample + validStart + initialSize, | |||||
buffer, | |||||
chan, 0, | |||||
(validEnd - validStart) - initialSize); | |||||
} | |||||
} | |||||
} | |||||
nextPlayPos += info.numSamples; | |||||
} | |||||
} | |||||
int64 BufferingAudioSource::getNextReadPosition() const | |||||
{ | |||||
jassert (source->getTotalLength() > 0); | |||||
return (source->isLooping() && nextPlayPos > 0) | |||||
? nextPlayPos % source->getTotalLength() | |||||
: nextPlayPos; | |||||
} | |||||
void BufferingAudioSource::setNextReadPosition (int64 newPosition) | |||||
{ | |||||
const ScopedLock sl (bufferStartPosLock); | |||||
nextPlayPos = newPosition; | |||||
backgroundThread.moveToFrontOfQueue (this); | |||||
} | |||||
bool BufferingAudioSource::readNextBufferChunk() | |||||
{ | |||||
int64 newBVS, newBVE, sectionToReadStart, sectionToReadEnd; | |||||
{ | |||||
const ScopedLock sl (bufferStartPosLock); | |||||
if (wasSourceLooping != isLooping()) | |||||
{ | |||||
wasSourceLooping = isLooping(); | |||||
bufferValidStart = 0; | |||||
bufferValidEnd = 0; | |||||
} | |||||
newBVS = jmax ((int64) 0, nextPlayPos); | |||||
newBVE = newBVS + buffer.getNumSamples() - 4; | |||||
sectionToReadStart = 0; | |||||
sectionToReadEnd = 0; | |||||
const int maxChunkSize = 2048; | |||||
if (newBVS < bufferValidStart || newBVS >= bufferValidEnd) | |||||
{ | |||||
newBVE = jmin (newBVE, newBVS + maxChunkSize); | |||||
sectionToReadStart = newBVS; | |||||
sectionToReadEnd = newBVE; | |||||
bufferValidStart = 0; | |||||
bufferValidEnd = 0; | |||||
} | |||||
else if (std::abs ((int) (newBVS - bufferValidStart)) > 512 | |||||
|| std::abs ((int) (newBVE - bufferValidEnd)) > 512) | |||||
{ | |||||
newBVE = jmin (newBVE, bufferValidEnd + maxChunkSize); | |||||
sectionToReadStart = bufferValidEnd; | |||||
sectionToReadEnd = newBVE; | |||||
bufferValidStart = newBVS; | |||||
bufferValidEnd = jmin (bufferValidEnd, newBVE); | |||||
} | |||||
} | |||||
if (sectionToReadStart != sectionToReadEnd) | |||||
{ | |||||
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; | |||||
} | |||||
else | |||||
{ | |||||
return false; | |||||
} | |||||
} | |||||
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,114 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_BUFFERINGAUDIOSOURCE_JUCEHEADER__ | |||||
#define __JUCE_BUFFERINGAUDIOSOURCE_JUCEHEADER__ | |||||
#include "juce_PositionableAudioSource.h" | |||||
//============================================================================== | |||||
/** | |||||
An AudioSource which takes another source as input, and buffers it using a thread. | |||||
Create this as a wrapper around another thread, and it will read-ahead with | |||||
a background thread to smooth out playback. You can either create one of these | |||||
directly, or use it indirectly using an AudioTransportSource. | |||||
@see PositionableAudioSource, AudioTransportSource | |||||
*/ | |||||
class JUCE_API BufferingAudioSource : public PositionableAudioSource, | |||||
private TimeSliceClient | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a BufferingAudioSource. | |||||
@param source the input source to read from | |||||
@param backgroundThread a background thread that will be used for the | |||||
background read-ahead. This object must not be deleted | |||||
until after any BufferedAudioSources that are using it | |||||
have been deleted! | |||||
@param deleteSourceWhenDeleted if true, then the input source object will | |||||
be deleted when this object is deleted | |||||
@param numberOfSamplesToBuffer the size of buffer to use for reading ahead | |||||
@param numberOfChannels the number of channels that will be played | |||||
*/ | |||||
BufferingAudioSource (PositionableAudioSource* source, | |||||
TimeSliceThread& backgroundThread, | |||||
bool deleteSourceWhenDeleted, | |||||
int numberOfSamplesToBuffer, | |||||
int numberOfChannels = 2); | |||||
/** Destructor. | |||||
The input source may be deleted depending on whether the deleteSourceWhenDeleted | |||||
flag was set in the constructor. | |||||
*/ | |||||
~BufferingAudioSource(); | |||||
//============================================================================== | |||||
/** Implementation of the AudioSource method. */ | |||||
void prepareToPlay (int samplesPerBlockExpected, double sampleRate); | |||||
/** Implementation of the AudioSource method. */ | |||||
void releaseResources(); | |||||
/** Implementation of the AudioSource method. */ | |||||
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill); | |||||
//============================================================================== | |||||
/** Implements the PositionableAudioSource method. */ | |||||
void setNextReadPosition (int64 newPosition); | |||||
/** Implements the PositionableAudioSource method. */ | |||||
int64 getNextReadPosition() const; | |||||
/** Implements the PositionableAudioSource method. */ | |||||
int64 getTotalLength() const { return source->getTotalLength(); } | |||||
/** Implements the PositionableAudioSource method. */ | |||||
bool isLooping() const { return source->isLooping(); } | |||||
private: | |||||
//============================================================================== | |||||
OptionalScopedPointer<PositionableAudioSource> source; | |||||
TimeSliceThread& backgroundThread; | |||||
int numberOfSamplesToBuffer, numberOfChannels; | |||||
AudioSampleBuffer buffer; | |||||
CriticalSection bufferStartPosLock; | |||||
int64 volatile bufferValidStart, bufferValidEnd, nextPlayPos; | |||||
double volatile sampleRate; | |||||
bool wasSourceLooping, isPrepared; | |||||
bool readNextBufferChunk(); | |||||
void readBufferSection (int64 start, int length, int bufferOffset); | |||||
int useTimeSlice(); | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BufferingAudioSource) | |||||
}; | |||||
#endif // __JUCE_BUFFERINGAUDIOSOURCE_JUCEHEADER__ |
@@ -0,0 +1,186 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
ChannelRemappingAudioSource::ChannelRemappingAudioSource (AudioSource* const source_, | |||||
const bool deleteSourceWhenDeleted) | |||||
: source (source_, deleteSourceWhenDeleted), | |||||
requiredNumberOfChannels (2), | |||||
buffer (2, 16) | |||||
{ | |||||
remappedInfo.buffer = &buffer; | |||||
remappedInfo.startSample = 0; | |||||
} | |||||
ChannelRemappingAudioSource::~ChannelRemappingAudioSource() {} | |||||
//============================================================================== | |||||
void ChannelRemappingAudioSource::setNumberOfChannelsToProduce (const int requiredNumberOfChannels_) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
requiredNumberOfChannels = requiredNumberOfChannels_; | |||||
} | |||||
void ChannelRemappingAudioSource::clearAllMappings() | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
remappedInputs.clear(); | |||||
remappedOutputs.clear(); | |||||
} | |||||
void ChannelRemappingAudioSource::setInputChannelMapping (const int destIndex, const int sourceIndex) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
while (remappedInputs.size() < destIndex) | |||||
remappedInputs.add (-1); | |||||
remappedInputs.set (destIndex, sourceIndex); | |||||
} | |||||
void ChannelRemappingAudioSource::setOutputChannelMapping (const int sourceIndex, const int destIndex) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
while (remappedOutputs.size() < sourceIndex) | |||||
remappedOutputs.add (-1); | |||||
remappedOutputs.set (sourceIndex, destIndex); | |||||
} | |||||
int ChannelRemappingAudioSource::getRemappedInputChannel (const int inputChannelIndex) const | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
if (inputChannelIndex >= 0 && inputChannelIndex < remappedInputs.size()) | |||||
return remappedInputs.getUnchecked (inputChannelIndex); | |||||
return -1; | |||||
} | |||||
int ChannelRemappingAudioSource::getRemappedOutputChannel (const int outputChannelIndex) const | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
if (outputChannelIndex >= 0 && outputChannelIndex < remappedOutputs.size()) | |||||
return remappedOutputs .getUnchecked (outputChannelIndex); | |||||
return -1; | |||||
} | |||||
//============================================================================== | |||||
void ChannelRemappingAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) | |||||
{ | |||||
source->prepareToPlay (samplesPerBlockExpected, sampleRate); | |||||
} | |||||
void ChannelRemappingAudioSource::releaseResources() | |||||
{ | |||||
source->releaseResources(); | |||||
} | |||||
void ChannelRemappingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
buffer.setSize (requiredNumberOfChannels, bufferToFill.numSamples, false, false, true); | |||||
const int numChans = bufferToFill.buffer->getNumChannels(); | |||||
for (int i = 0; i < buffer.getNumChannels(); ++i) | |||||
{ | |||||
const int remappedChan = getRemappedInputChannel (i); | |||||
if (remappedChan >= 0 && remappedChan < numChans) | |||||
{ | |||||
buffer.copyFrom (i, 0, *bufferToFill.buffer, | |||||
remappedChan, | |||||
bufferToFill.startSample, | |||||
bufferToFill.numSamples); | |||||
} | |||||
else | |||||
{ | |||||
buffer.clear (i, 0, bufferToFill.numSamples); | |||||
} | |||||
} | |||||
remappedInfo.numSamples = bufferToFill.numSamples; | |||||
source->getNextAudioBlock (remappedInfo); | |||||
bufferToFill.clearActiveBufferRegion(); | |||||
for (int i = 0; i < requiredNumberOfChannels; ++i) | |||||
{ | |||||
const int remappedChan = getRemappedOutputChannel (i); | |||||
if (remappedChan >= 0 && remappedChan < numChans) | |||||
{ | |||||
bufferToFill.buffer->addFrom (remappedChan, bufferToFill.startSample, | |||||
buffer, i, 0, bufferToFill.numSamples); | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
XmlElement* ChannelRemappingAudioSource::createXml() const | |||||
{ | |||||
XmlElement* e = new XmlElement ("MAPPINGS"); | |||||
String ins, outs; | |||||
const ScopedLock sl (lock); | |||||
for (int i = 0; i < remappedInputs.size(); ++i) | |||||
ins << remappedInputs.getUnchecked(i) << ' '; | |||||
for (int i = 0; i < remappedOutputs.size(); ++i) | |||||
outs << remappedOutputs.getUnchecked(i) << ' '; | |||||
e->setAttribute ("inputs", ins.trimEnd()); | |||||
e->setAttribute ("outputs", outs.trimEnd()); | |||||
return e; | |||||
} | |||||
void ChannelRemappingAudioSource::restoreFromXml (const XmlElement& e) | |||||
{ | |||||
if (e.hasTagName ("MAPPINGS")) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
clearAllMappings(); | |||||
StringArray ins, outs; | |||||
ins.addTokens (e.getStringAttribute ("inputs"), false); | |||||
outs.addTokens (e.getStringAttribute ("outputs"), false); | |||||
for (int i = 0; i < ins.size(); ++i) | |||||
remappedInputs.add (ins[i].getIntValue()); | |||||
for (int i = 0; i < outs.size(); ++i) | |||||
remappedOutputs.add (outs[i].getIntValue()); | |||||
} | |||||
} |
@@ -0,0 +1,149 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_CHANNELREMAPPINGAUDIOSOURCE_JUCEHEADER__ | |||||
#define __JUCE_CHANNELREMAPPINGAUDIOSOURCE_JUCEHEADER__ | |||||
#include "juce_AudioSource.h" | |||||
//============================================================================== | |||||
/** | |||||
An AudioSource that takes the audio from another source, and re-maps its | |||||
input and output channels to a different arrangement. | |||||
You can use this to increase or decrease the number of channels that an | |||||
audio source uses, or to re-order those channels. | |||||
Call the reset() method before using it to set up a default mapping, and then | |||||
the setInputChannelMapping() and setOutputChannelMapping() methods to | |||||
create an appropriate mapping, otherwise no channels will be connected and | |||||
it'll produce silence. | |||||
@see AudioSource | |||||
*/ | |||||
class ChannelRemappingAudioSource : public AudioSource | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a remapping source that will pass on audio from the given input. | |||||
@param source the input source to use. Make sure that this doesn't | |||||
get deleted before the ChannelRemappingAudioSource object | |||||
@param deleteSourceWhenDeleted if true, the input source will be deleted | |||||
when this object is deleted, if false, the caller is | |||||
responsible for its deletion | |||||
*/ | |||||
ChannelRemappingAudioSource (AudioSource* source, | |||||
bool deleteSourceWhenDeleted); | |||||
/** Destructor. */ | |||||
~ChannelRemappingAudioSource(); | |||||
//============================================================================== | |||||
/** Specifies a number of channels that this audio source must produce from its | |||||
getNextAudioBlock() callback. | |||||
*/ | |||||
void setNumberOfChannelsToProduce (int requiredNumberOfChannels); | |||||
/** Clears any mapped channels. | |||||
After this, no channels are mapped, so this object will produce silence. Create | |||||
some mappings with setInputChannelMapping() and setOutputChannelMapping(). | |||||
*/ | |||||
void clearAllMappings(); | |||||
/** Creates an input channel mapping. | |||||
When the getNextAudioBlock() method is called, the data in channel sourceChannelIndex of the incoming | |||||
data will be sent to destChannelIndex of our input source. | |||||
@param destChannelIndex the index of an input channel in our input audio source (i.e. the | |||||
source specified when this object was created). | |||||
@param sourceChannelIndex the index of the input channel in the incoming audio data buffer | |||||
during our getNextAudioBlock() callback | |||||
*/ | |||||
void setInputChannelMapping (int destChannelIndex, | |||||
int sourceChannelIndex); | |||||
/** Creates an output channel mapping. | |||||
When the getNextAudioBlock() method is called, the data returned in channel sourceChannelIndex by | |||||
our input audio source will be copied to channel destChannelIndex of the final buffer. | |||||
@param sourceChannelIndex the index of an output channel coming from our input audio source | |||||
(i.e. the source specified when this object was created). | |||||
@param destChannelIndex the index of the output channel in the incoming audio data buffer | |||||
during our getNextAudioBlock() callback | |||||
*/ | |||||
void setOutputChannelMapping (int sourceChannelIndex, | |||||
int destChannelIndex); | |||||
/** Returns the channel from our input that will be sent to channel inputChannelIndex of | |||||
our input audio source. | |||||
*/ | |||||
int getRemappedInputChannel (int inputChannelIndex) const; | |||||
/** Returns the output channel to which channel outputChannelIndex of our input audio | |||||
source will be sent to. | |||||
*/ | |||||
int getRemappedOutputChannel (int outputChannelIndex) const; | |||||
//============================================================================== | |||||
/** Returns an XML object to encapsulate the state of the mappings. | |||||
@see restoreFromXml | |||||
*/ | |||||
XmlElement* createXml() const; | |||||
/** Restores the mappings from an XML object created by createXML(). | |||||
@see createXml | |||||
*/ | |||||
void restoreFromXml (const XmlElement& e); | |||||
//============================================================================== | |||||
void prepareToPlay (int samplesPerBlockExpected, double sampleRate); | |||||
void releaseResources(); | |||||
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill); | |||||
private: | |||||
//============================================================================== | |||||
OptionalScopedPointer<AudioSource> source; | |||||
Array <int> remappedInputs, remappedOutputs; | |||||
int requiredNumberOfChannels; | |||||
AudioSampleBuffer buffer; | |||||
AudioSourceChannelInfo remappedInfo; | |||||
CriticalSection lock; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChannelRemappingAudioSource) | |||||
}; | |||||
#endif // __JUCE_CHANNELREMAPPINGAUDIOSOURCE_JUCEHEADER__ |
@@ -0,0 +1,72 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce 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::setFilterParameters (const IIRFilter& newSettings) | |||||
{ | |||||
for (int i = iirFilters.size(); --i >= 0;) | |||||
iirFilters.getUnchecked(i)->copyCoefficientsFrom (newSettings); | |||||
} | |||||
//============================================================================== | |||||
void IIRFilterAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) | |||||
{ | |||||
input->prepareToPlay (samplesPerBlockExpected, sampleRate); | |||||
for (int i = iirFilters.size(); --i >= 0;) | |||||
iirFilters.getUnchecked(i)->reset(); | |||||
} | |||||
void IIRFilterAudioSource::releaseResources() | |||||
{ | |||||
input->releaseResources(); | |||||
} | |||||
void IIRFilterAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) | |||||
{ | |||||
input->getNextAudioBlock (bufferToFill); | |||||
const int numChannels = bufferToFill.buffer->getNumChannels(); | |||||
while (numChannels > iirFilters.size()) | |||||
iirFilters.add (new IIRFilter (*iirFilters.getUnchecked (0))); | |||||
for (int i = 0; i < numChannels; ++i) | |||||
iirFilters.getUnchecked(i) | |||||
->processSamples (bufferToFill.buffer->getSampleData (i, bufferToFill.startSample), | |||||
bufferToFill.numSamples); | |||||
} |
@@ -0,0 +1,71 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_IIRFILTERAUDIOSOURCE_JUCEHEADER__ | |||||
#define __JUCE_IIRFILTERAUDIOSOURCE_JUCEHEADER__ | |||||
#include "juce_AudioSource.h" | |||||
#include "../effects/juce_IIRFilter.h" | |||||
//============================================================================== | |||||
/** | |||||
An AudioSource that performs an IIR filter on another source. | |||||
*/ | |||||
class JUCE_API IIRFilterAudioSource : public AudioSource | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a IIRFilterAudioSource for a given input source. | |||||
@param inputSource the input source to read from - this must not be null | |||||
@param deleteInputWhenDeleted if true, the input source will be deleted when | |||||
this object is deleted | |||||
*/ | |||||
IIRFilterAudioSource (AudioSource* inputSource, | |||||
bool deleteInputWhenDeleted); | |||||
/** Destructor. */ | |||||
~IIRFilterAudioSource(); | |||||
//============================================================================== | |||||
/** Changes the filter to use the same parameters as the one being passed in. */ | |||||
void setFilterParameters (const IIRFilter& newSettings); | |||||
//============================================================================== | |||||
void prepareToPlay (int samplesPerBlockExpected, double sampleRate); | |||||
void releaseResources(); | |||||
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill); | |||||
private: | |||||
//============================================================================== | |||||
OptionalScopedPointer<AudioSource> input; | |||||
OwnedArray <IIRFilter> iirFilters; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (IIRFilterAudioSource) | |||||
}; | |||||
#endif // __JUCE_IIRFILTERAUDIOSOURCE_JUCEHEADER__ |
@@ -0,0 +1,158 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
MixerAudioSource::MixerAudioSource() | |||||
: tempBuffer (2, 0), | |||||
currentSampleRate (0.0), | |||||
bufferSizeExpected (0) | |||||
{ | |||||
} | |||||
MixerAudioSource::~MixerAudioSource() | |||||
{ | |||||
removeAllInputs(); | |||||
} | |||||
//============================================================================== | |||||
void MixerAudioSource::addInputSource (AudioSource* input, const bool deleteWhenRemoved) | |||||
{ | |||||
if (input != nullptr && ! inputs.contains (input)) | |||||
{ | |||||
double localRate; | |||||
int localBufferSize; | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
localRate = currentSampleRate; | |||||
localBufferSize = bufferSizeExpected; | |||||
} | |||||
if (localRate > 0.0) | |||||
input->prepareToPlay (localBufferSize, localRate); | |||||
const ScopedLock sl (lock); | |||||
inputsToDelete.setBit (inputs.size(), deleteWhenRemoved); | |||||
inputs.add (input); | |||||
} | |||||
} | |||||
void MixerAudioSource::removeInputSource (AudioSource* const input) | |||||
{ | |||||
if (input != nullptr) | |||||
{ | |||||
ScopedPointer<AudioSource> toDelete; | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
const int index = inputs.indexOf (input); | |||||
if (index < 0) | |||||
return; | |||||
if (inputsToDelete [index]) | |||||
toDelete = input; | |||||
inputsToDelete.shiftBits (-1, index); | |||||
inputs.remove (index); | |||||
} | |||||
input->releaseResources(); | |||||
} | |||||
} | |||||
void MixerAudioSource::removeAllInputs() | |||||
{ | |||||
OwnedArray<AudioSource> toDelete; | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
for (int i = inputs.size(); --i >= 0;) | |||||
if (inputsToDelete[i]) | |||||
toDelete.add (inputs.getUnchecked(i)); | |||||
inputs.clear(); | |||||
} | |||||
for (int i = toDelete.size(); --i >= 0;) | |||||
toDelete.getUnchecked(i)->releaseResources(); | |||||
} | |||||
void MixerAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) | |||||
{ | |||||
tempBuffer.setSize (2, samplesPerBlockExpected); | |||||
const ScopedLock sl (lock); | |||||
currentSampleRate = sampleRate; | |||||
bufferSizeExpected = samplesPerBlockExpected; | |||||
for (int i = inputs.size(); --i >= 0;) | |||||
inputs.getUnchecked(i)->prepareToPlay (samplesPerBlockExpected, sampleRate); | |||||
} | |||||
void MixerAudioSource::releaseResources() | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
for (int i = inputs.size(); --i >= 0;) | |||||
inputs.getUnchecked(i)->releaseResources(); | |||||
tempBuffer.setSize (2, 0); | |||||
currentSampleRate = 0; | |||||
bufferSizeExpected = 0; | |||||
} | |||||
void MixerAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
if (inputs.size() > 0) | |||||
{ | |||||
inputs.getUnchecked(0)->getNextAudioBlock (info); | |||||
if (inputs.size() > 1) | |||||
{ | |||||
tempBuffer.setSize (jmax (1, info.buffer->getNumChannels()), | |||||
info.buffer->getNumSamples()); | |||||
AudioSourceChannelInfo info2 (&tempBuffer, 0, info.numSamples); | |||||
for (int i = 1; i < inputs.size(); ++i) | |||||
{ | |||||
inputs.getUnchecked(i)->getNextAudioBlock (info2); | |||||
for (int chan = 0; chan < info.buffer->getNumChannels(); ++chan) | |||||
info.buffer->addFrom (chan, info.startSample, tempBuffer, chan, 0, info.numSamples); | |||||
} | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
info.clearActiveBufferRegion(); | |||||
} | |||||
} |
@@ -0,0 +1,104 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_MIXERAUDIOSOURCE_JUCEHEADER__ | |||||
#define __JUCE_MIXERAUDIOSOURCE_JUCEHEADER__ | |||||
#include "juce_AudioSource.h" | |||||
//============================================================================== | |||||
/** | |||||
An AudioSource that mixes together the output of a set of other AudioSources. | |||||
Input sources can be added and removed while the mixer is running as long as their | |||||
prepareToPlay() and releaseResources() methods are called before and after adding | |||||
them to the mixer. | |||||
*/ | |||||
class JUCE_API MixerAudioSource : public AudioSource | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a MixerAudioSource. */ | |||||
MixerAudioSource(); | |||||
/** Destructor. */ | |||||
~MixerAudioSource(); | |||||
//============================================================================== | |||||
/** Adds an input source to the mixer. | |||||
If the mixer is running you'll need to make sure that the input source | |||||
is ready to play by calling its prepareToPlay() method before adding it. | |||||
If the mixer is stopped, then its input sources will be automatically | |||||
prepared when the mixer's prepareToPlay() method is called. | |||||
@param newInput the source to add to the mixer | |||||
@param deleteWhenRemoved if true, then this source will be deleted when | |||||
no longer needed by the mixer. | |||||
*/ | |||||
void addInputSource (AudioSource* newInput, bool deleteWhenRemoved); | |||||
/** Removes an input source. | |||||
If the source was added by calling addInputSource() with the deleteWhenRemoved | |||||
flag set, it will be deleted by this method. | |||||
*/ | |||||
void removeInputSource (AudioSource* input); | |||||
/** Removes all the input sources. | |||||
Any sources which were added by calling addInputSource() with the deleteWhenRemoved | |||||
flag set will be deleted by this method. | |||||
*/ | |||||
void removeAllInputs(); | |||||
//============================================================================== | |||||
/** Implementation of the AudioSource method. | |||||
This will call prepareToPlay() on all its input sources. | |||||
*/ | |||||
void prepareToPlay (int samplesPerBlockExpected, double sampleRate); | |||||
/** Implementation of the AudioSource method. | |||||
This will call releaseResources() on all its input sources. | |||||
*/ | |||||
void releaseResources(); | |||||
/** Implementation of the AudioSource method. */ | |||||
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill); | |||||
private: | |||||
//============================================================================== | |||||
Array <AudioSource*> inputs; | |||||
BigInteger inputsToDelete; | |||||
CriticalSection lock; | |||||
AudioSampleBuffer tempBuffer; | |||||
double currentSampleRate; | |||||
int bufferSizeExpected; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MixerAudioSource) | |||||
}; | |||||
#endif // __JUCE_MIXERAUDIOSOURCE_JUCEHEADER__ |
@@ -0,0 +1,81 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_POSITIONABLEAUDIOSOURCE_JUCEHEADER__ | |||||
#define __JUCE_POSITIONABLEAUDIOSOURCE_JUCEHEADER__ | |||||
#include "juce_AudioSource.h" | |||||
//============================================================================== | |||||
/** | |||||
A type of AudioSource which can be repositioned. | |||||
The basic AudioSource just streams continuously with no idea of a current | |||||
time or length, so the PositionableAudioSource is used for a finite stream | |||||
that has a current read position. | |||||
@see AudioSource, AudioTransportSource | |||||
*/ | |||||
class JUCE_API PositionableAudioSource : public AudioSource | |||||
{ | |||||
protected: | |||||
//============================================================================== | |||||
/** Creates the PositionableAudioSource. */ | |||||
PositionableAudioSource() noexcept {} | |||||
public: | |||||
/** Destructor */ | |||||
~PositionableAudioSource() {} | |||||
//============================================================================== | |||||
/** Tells the stream to move to a new position. | |||||
Calling this indicates that the next call to AudioSource::getNextAudioBlock() | |||||
should return samples from this position. | |||||
Note that this may be called on a different thread to getNextAudioBlock(), | |||||
so the subclass should make sure it's synchronised. | |||||
*/ | |||||
virtual void setNextReadPosition (int64 newPosition) = 0; | |||||
/** Returns the position from which the next block will be returned. | |||||
@see setNextReadPosition | |||||
*/ | |||||
virtual int64 getNextReadPosition() const = 0; | |||||
/** Returns the total length of the stream (in samples). */ | |||||
virtual int64 getTotalLength() const = 0; | |||||
/** Returns true if this source is actually playing in a loop. */ | |||||
virtual bool isLooping() const = 0; | |||||
/** Tells the source whether you'd like it to play in a loop. */ | |||||
virtual void setLooping (bool shouldLoop) { (void) shouldLoop; } | |||||
}; | |||||
#endif // __JUCE_POSITIONABLEAUDIOSOURCE_JUCEHEADER__ |
@@ -0,0 +1,254 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
ResamplingAudioSource::ResamplingAudioSource (AudioSource* const inputSource, | |||||
const bool deleteInputWhenDeleted, | |||||
const int numChannels_) | |||||
: input (inputSource, deleteInputWhenDeleted), | |||||
ratio (1.0), | |||||
lastRatio (1.0), | |||||
buffer (numChannels_, 0), | |||||
sampsInBuffer (0), | |||||
numChannels (numChannels_) | |||||
{ | |||||
jassert (input != nullptr); | |||||
} | |||||
ResamplingAudioSource::~ResamplingAudioSource() {} | |||||
void ResamplingAudioSource::setResamplingRatio (const double samplesInPerOutputSample) | |||||
{ | |||||
jassert (samplesInPerOutputSample > 0); | |||||
const SpinLock::ScopedLockType sl (ratioLock); | |||||
ratio = jmax (0.0, samplesInPerOutputSample); | |||||
} | |||||
void ResamplingAudioSource::prepareToPlay (int samplesPerBlockExpected, | |||||
double sampleRate) | |||||
{ | |||||
const SpinLock::ScopedLockType sl (ratioLock); | |||||
input->prepareToPlay (samplesPerBlockExpected, sampleRate); | |||||
buffer.setSize (numChannels, roundToInt (samplesPerBlockExpected * ratio) + 32); | |||||
buffer.clear(); | |||||
sampsInBuffer = 0; | |||||
bufferPos = 0; | |||||
subSampleOffset = 0.0; | |||||
filterStates.calloc ((size_t) numChannels); | |||||
srcBuffers.calloc ((size_t) numChannels); | |||||
destBuffers.calloc ((size_t) numChannels); | |||||
createLowPass (ratio); | |||||
resetFilters(); | |||||
} | |||||
void ResamplingAudioSource::releaseResources() | |||||
{ | |||||
input->releaseResources(); | |||||
buffer.setSize (numChannels, 0); | |||||
} | |||||
void ResamplingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) | |||||
{ | |||||
double localRatio; | |||||
{ | |||||
const SpinLock::ScopedLockType sl (ratioLock); | |||||
localRatio = ratio; | |||||
} | |||||
if (lastRatio != localRatio) | |||||
{ | |||||
createLowPass (localRatio); | |||||
lastRatio = localRatio; | |||||
} | |||||
const int sampsNeeded = roundToInt (info.numSamples * localRatio) + 2; | |||||
int bufferSize = buffer.getNumSamples(); | |||||
if (bufferSize < sampsNeeded + 8) | |||||
{ | |||||
bufferPos %= bufferSize; | |||||
bufferSize = sampsNeeded + 32; | |||||
buffer.setSize (buffer.getNumChannels(), bufferSize, true, true); | |||||
} | |||||
bufferPos %= bufferSize; | |||||
int endOfBufferPos = bufferPos + sampsInBuffer; | |||||
const int channelsToProcess = jmin (numChannels, info.buffer->getNumChannels()); | |||||
while (sampsNeeded > sampsInBuffer) | |||||
{ | |||||
endOfBufferPos %= bufferSize; | |||||
int numToDo = jmin (sampsNeeded - sampsInBuffer, | |||||
bufferSize - endOfBufferPos); | |||||
AudioSourceChannelInfo readInfo (&buffer, endOfBufferPos, numToDo); | |||||
input->getNextAudioBlock (readInfo); | |||||
if (localRatio > 1.0001) | |||||
{ | |||||
// for down-sampling, pre-apply the filter.. | |||||
for (int i = channelsToProcess; --i >= 0;) | |||||
applyFilter (buffer.getSampleData (i, endOfBufferPos), numToDo, filterStates[i]); | |||||
} | |||||
sampsInBuffer += numToDo; | |||||
endOfBufferPos += numToDo; | |||||
} | |||||
for (int channel = 0; channel < channelsToProcess; ++channel) | |||||
{ | |||||
destBuffers[channel] = info.buffer->getSampleData (channel, info.startSample); | |||||
srcBuffers[channel] = buffer.getSampleData (channel, 0); | |||||
} | |||||
int nextPos = (bufferPos + 1) % bufferSize; | |||||
for (int m = info.numSamples; --m >= 0;) | |||||
{ | |||||
const float alpha = (float) subSampleOffset; | |||||
for (int channel = 0; channel < channelsToProcess; ++channel) | |||||
*destBuffers[channel]++ = srcBuffers[channel][bufferPos] | |||||
+ alpha * (srcBuffers[channel][nextPos] - srcBuffers[channel][bufferPos]); | |||||
subSampleOffset += localRatio; | |||||
jassert (sampsInBuffer > 0); | |||||
while (subSampleOffset >= 1.0) | |||||
{ | |||||
if (++bufferPos >= bufferSize) | |||||
bufferPos = 0; | |||||
--sampsInBuffer; | |||||
nextPos = (bufferPos + 1) % bufferSize; | |||||
subSampleOffset -= 1.0; | |||||
} | |||||
} | |||||
if (localRatio < 0.9999) | |||||
{ | |||||
// for up-sampling, apply the filter after transposing.. | |||||
for (int i = channelsToProcess; --i >= 0;) | |||||
applyFilter (info.buffer->getSampleData (i, info.startSample), info.numSamples, filterStates[i]); | |||||
} | |||||
else if (localRatio <= 1.0001 && info.numSamples > 0) | |||||
{ | |||||
// if the filter's not currently being applied, keep it stoked with the last couple of samples to avoid discontinuities | |||||
for (int i = channelsToProcess; --i >= 0;) | |||||
{ | |||||
const float* const endOfBuffer = info.buffer->getSampleData (i, info.startSample + info.numSamples - 1); | |||||
FilterState& fs = filterStates[i]; | |||||
if (info.numSamples > 1) | |||||
{ | |||||
fs.y2 = fs.x2 = *(endOfBuffer - 1); | |||||
} | |||||
else | |||||
{ | |||||
fs.y2 = fs.y1; | |||||
fs.x2 = fs.x1; | |||||
} | |||||
fs.y1 = fs.x1 = *endOfBuffer; | |||||
} | |||||
} | |||||
jassert (sampsInBuffer >= 0); | |||||
} | |||||
void ResamplingAudioSource::createLowPass (const double frequencyRatio) | |||||
{ | |||||
const double proportionalRate = (frequencyRatio > 1.0) ? 0.5 / frequencyRatio | |||||
: 0.5 * frequencyRatio; | |||||
const double n = 1.0 / std::tan (double_Pi * jmax (0.001, proportionalRate)); | |||||
const double nSquared = n * n; | |||||
const double c1 = 1.0 / (1.0 + std::sqrt (2.0) * n + nSquared); | |||||
setFilterCoefficients (c1, | |||||
c1 * 2.0f, | |||||
c1, | |||||
1.0, | |||||
c1 * 2.0 * (1.0 - nSquared), | |||||
c1 * (1.0 - std::sqrt (2.0) * n + nSquared)); | |||||
} | |||||
void ResamplingAudioSource::setFilterCoefficients (double c1, double c2, double c3, double c4, double c5, double c6) | |||||
{ | |||||
const double a = 1.0 / c4; | |||||
c1 *= a; | |||||
c2 *= a; | |||||
c3 *= a; | |||||
c5 *= a; | |||||
c6 *= a; | |||||
coefficients[0] = c1; | |||||
coefficients[1] = c2; | |||||
coefficients[2] = c3; | |||||
coefficients[3] = c4; | |||||
coefficients[4] = c5; | |||||
coefficients[5] = c6; | |||||
} | |||||
void ResamplingAudioSource::resetFilters() | |||||
{ | |||||
filterStates.clear ((size_t) numChannels); | |||||
} | |||||
void ResamplingAudioSource::applyFilter (float* samples, int num, FilterState& fs) | |||||
{ | |||||
while (--num >= 0) | |||||
{ | |||||
const double in = *samples; | |||||
double out = coefficients[0] * in | |||||
+ coefficients[1] * fs.x1 | |||||
+ coefficients[2] * fs.x2 | |||||
- coefficients[4] * fs.y1 | |||||
- coefficients[5] * fs.y2; | |||||
#if JUCE_INTEL | |||||
if (! (out < -1.0e-8 || out > 1.0e-8)) | |||||
out = 0; | |||||
#endif | |||||
fs.x2 = fs.x1; | |||||
fs.x1 = in; | |||||
fs.y2 = fs.y1; | |||||
fs.y1 = out; | |||||
*samples++ = (float) out; | |||||
} | |||||
} |
@@ -0,0 +1,106 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_RESAMPLINGAUDIOSOURCE_JUCEHEADER__ | |||||
#define __JUCE_RESAMPLINGAUDIOSOURCE_JUCEHEADER__ | |||||
#include "juce_AudioSource.h" | |||||
//============================================================================== | |||||
/** | |||||
A type of AudioSource that takes an input source and changes its sample rate. | |||||
@see AudioSource | |||||
*/ | |||||
class JUCE_API ResamplingAudioSource : public AudioSource | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a ResamplingAudioSource for a given input source. | |||||
@param inputSource the input source to read from | |||||
@param deleteInputWhenDeleted if true, the input source will be deleted when | |||||
this object is deleted | |||||
@param numChannels the number of channels to process | |||||
*/ | |||||
ResamplingAudioSource (AudioSource* inputSource, | |||||
bool deleteInputWhenDeleted, | |||||
int numChannels = 2); | |||||
/** Destructor. */ | |||||
~ResamplingAudioSource(); | |||||
/** Changes the resampling ratio. | |||||
(This value can be changed at any time, even while the source is running). | |||||
@param samplesInPerOutputSample if set to 1.0, the input is passed through; higher | |||||
values will speed it up; lower values will slow it | |||||
down. The ratio must be greater than 0 | |||||
*/ | |||||
void setResamplingRatio (double samplesInPerOutputSample); | |||||
/** Returns the current resampling ratio. | |||||
This is the value that was set by setResamplingRatio(). | |||||
*/ | |||||
double getResamplingRatio() const noexcept { return ratio; } | |||||
//============================================================================== | |||||
void prepareToPlay (int samplesPerBlockExpected, double sampleRate); | |||||
void releaseResources(); | |||||
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill); | |||||
private: | |||||
//============================================================================== | |||||
OptionalScopedPointer<AudioSource> input; | |||||
double ratio, lastRatio; | |||||
AudioSampleBuffer buffer; | |||||
int bufferPos, sampsInBuffer; | |||||
double subSampleOffset; | |||||
double coefficients[6]; | |||||
SpinLock ratioLock; | |||||
const int numChannels; | |||||
HeapBlock<float*> destBuffers, srcBuffers; | |||||
void setFilterCoefficients (double c1, double c2, double c3, double c4, double c5, double c6); | |||||
void createLowPass (double proportionalRate); | |||||
struct FilterState | |||||
{ | |||||
double x1, x2, y1, y2; | |||||
}; | |||||
HeapBlock<FilterState> filterStates; | |||||
void resetFilters(); | |||||
void applyFilter (float* samples, int num, FilterState& fs); | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ResamplingAudioSource) | |||||
}; | |||||
#endif // __JUCE_RESAMPLINGAUDIOSOURCE_JUCEHEADER__ |
@@ -0,0 +1,81 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
ReverbAudioSource::ReverbAudioSource (AudioSource* const inputSource, const bool deleteInputWhenDeleted) | |||||
: input (inputSource, deleteInputWhenDeleted), | |||||
bypass (false) | |||||
{ | |||||
jassert (inputSource != nullptr); | |||||
} | |||||
ReverbAudioSource::~ReverbAudioSource() {} | |||||
void ReverbAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
input->prepareToPlay (samplesPerBlockExpected, sampleRate); | |||||
reverb.setSampleRate (sampleRate); | |||||
} | |||||
void ReverbAudioSource::releaseResources() {} | |||||
void ReverbAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
input->getNextAudioBlock (bufferToFill); | |||||
if (! bypass) | |||||
{ | |||||
float* const firstChannel = bufferToFill.buffer->getSampleData (0, bufferToFill.startSample); | |||||
if (bufferToFill.buffer->getNumChannels() > 1) | |||||
{ | |||||
reverb.processStereo (firstChannel, | |||||
bufferToFill.buffer->getSampleData (1, bufferToFill.startSample), | |||||
bufferToFill.numSamples); | |||||
} | |||||
else | |||||
{ | |||||
reverb.processMono (firstChannel, bufferToFill.numSamples); | |||||
} | |||||
} | |||||
} | |||||
void ReverbAudioSource::setParameters (const Reverb::Parameters& newParams) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
reverb.setParameters (newParams); | |||||
} | |||||
void ReverbAudioSource::setBypassed (bool b) noexcept | |||||
{ | |||||
if (bypass != b) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
bypass = b; | |||||
reverb.reset(); | |||||
} | |||||
} |
@@ -0,0 +1,80 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_REVERBAUDIOSOURCE_JUCEHEADER__ | |||||
#define __JUCE_REVERBAUDIOSOURCE_JUCEHEADER__ | |||||
#include "juce_AudioSource.h" | |||||
#include "../effects/juce_Reverb.h" | |||||
//============================================================================== | |||||
/** | |||||
An AudioSource that uses the Reverb class to apply a reverb to another AudioSource. | |||||
@see Reverb | |||||
*/ | |||||
class JUCE_API ReverbAudioSource : public AudioSource | |||||
{ | |||||
public: | |||||
/** Creates a ReverbAudioSource to process a given input source. | |||||
@param inputSource the input source to read from - this must not be null | |||||
@param deleteInputWhenDeleted if true, the input source will be deleted when | |||||
this object is deleted | |||||
*/ | |||||
ReverbAudioSource (AudioSource* inputSource, | |||||
bool deleteInputWhenDeleted); | |||||
/** Destructor. */ | |||||
~ReverbAudioSource(); | |||||
//============================================================================== | |||||
/** Returns the parameters from the reverb. */ | |||||
const Reverb::Parameters& getParameters() const noexcept { return reverb.getParameters(); } | |||||
/** Changes the reverb's parameters. */ | |||||
void setParameters (const Reverb::Parameters& newParams); | |||||
void setBypassed (bool isBypassed) noexcept; | |||||
bool isBypassed() const noexcept { return bypass; } | |||||
//============================================================================== | |||||
void prepareToPlay (int samplesPerBlockExpected, double sampleRate); | |||||
void releaseResources(); | |||||
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill); | |||||
private: | |||||
//============================================================================== | |||||
CriticalSection lock; | |||||
OptionalScopedPointer<AudioSource> input; | |||||
Reverb reverb; | |||||
volatile bool bypass; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ReverbAudioSource) | |||||
}; | |||||
#endif // __JUCE_REVERBAUDIOSOURCE_JUCEHEADER__ |
@@ -0,0 +1,77 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
ToneGeneratorAudioSource::ToneGeneratorAudioSource() | |||||
: frequency (1000.0), | |||||
sampleRate (44100.0), | |||||
currentPhase (0.0), | |||||
phasePerSample (0.0), | |||||
amplitude (0.5f) | |||||
{ | |||||
} | |||||
ToneGeneratorAudioSource::~ToneGeneratorAudioSource() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
void ToneGeneratorAudioSource::setAmplitude (const float newAmplitude) | |||||
{ | |||||
amplitude = newAmplitude; | |||||
} | |||||
void ToneGeneratorAudioSource::setFrequency (const double newFrequencyHz) | |||||
{ | |||||
frequency = newFrequencyHz; | |||||
phasePerSample = 0.0; | |||||
} | |||||
//============================================================================== | |||||
void ToneGeneratorAudioSource::prepareToPlay (int /*samplesPerBlockExpected*/, | |||||
double sampleRate_) | |||||
{ | |||||
currentPhase = 0.0; | |||||
phasePerSample = 0.0; | |||||
sampleRate = sampleRate_; | |||||
} | |||||
void ToneGeneratorAudioSource::releaseResources() | |||||
{ | |||||
} | |||||
void ToneGeneratorAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) | |||||
{ | |||||
if (phasePerSample == 0.0) | |||||
phasePerSample = double_Pi * 2.0 / (sampleRate / frequency); | |||||
for (int i = 0; i < info.numSamples; ++i) | |||||
{ | |||||
const float sample = amplitude * (float) std::sin (currentPhase); | |||||
currentPhase += phasePerSample; | |||||
for (int j = info.buffer->getNumChannels(); --j >= 0;) | |||||
*info.buffer->getSampleData (j, info.startSample + i) = sample; | |||||
} | |||||
} |
@@ -0,0 +1,76 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_TONEGENERATORAUDIOSOURCE_JUCEHEADER__ | |||||
#define __JUCE_TONEGENERATORAUDIOSOURCE_JUCEHEADER__ | |||||
#include "juce_AudioSource.h" | |||||
//============================================================================== | |||||
/** | |||||
A simple AudioSource that generates a sine wave. | |||||
*/ | |||||
class JUCE_API ToneGeneratorAudioSource : public AudioSource | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a ToneGeneratorAudioSource. */ | |||||
ToneGeneratorAudioSource(); | |||||
/** Destructor. */ | |||||
~ToneGeneratorAudioSource(); | |||||
//============================================================================== | |||||
/** Sets the signal's amplitude. */ | |||||
void setAmplitude (float newAmplitude); | |||||
/** Sets the signal's frequency. */ | |||||
void setFrequency (double newFrequencyHz); | |||||
//============================================================================== | |||||
/** Implementation of the AudioSource method. */ | |||||
void prepareToPlay (int samplesPerBlockExpected, double sampleRate); | |||||
/** Implementation of the AudioSource method. */ | |||||
void releaseResources(); | |||||
/** Implementation of the AudioSource method. */ | |||||
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill); | |||||
private: | |||||
//============================================================================== | |||||
double frequency, sampleRate; | |||||
double currentPhase, phasePerSample; | |||||
float amplitude; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToneGeneratorAudioSource) | |||||
}; | |||||
#endif // __JUCE_TONEGENERATORAUDIOSOURCE_JUCEHEADER__ |
@@ -0,0 +1,433 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
SynthesiserSound::SynthesiserSound() | |||||
{ | |||||
} | |||||
SynthesiserSound::~SynthesiserSound() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
SynthesiserVoice::SynthesiserVoice() | |||||
: currentSampleRate (44100.0), | |||||
currentlyPlayingNote (-1), | |||||
noteOnTime (0), | |||||
keyIsDown (false), | |||||
sostenutoPedalDown (false) | |||||
{ | |||||
} | |||||
SynthesiserVoice::~SynthesiserVoice() | |||||
{ | |||||
} | |||||
bool SynthesiserVoice::isPlayingChannel (const int midiChannel) const | |||||
{ | |||||
return currentlyPlayingSound != nullptr | |||||
&& currentlyPlayingSound->appliesToChannel (midiChannel); | |||||
} | |||||
void SynthesiserVoice::setCurrentPlaybackSampleRate (const double newRate) | |||||
{ | |||||
currentSampleRate = newRate; | |||||
} | |||||
void SynthesiserVoice::clearCurrentNote() | |||||
{ | |||||
currentlyPlayingNote = -1; | |||||
currentlyPlayingSound = nullptr; | |||||
} | |||||
//============================================================================== | |||||
Synthesiser::Synthesiser() | |||||
: sampleRate (0), | |||||
lastNoteOnCounter (0), | |||||
shouldStealNotes (true) | |||||
{ | |||||
for (int i = 0; i < numElementsInArray (lastPitchWheelValues); ++i) | |||||
lastPitchWheelValues[i] = 0x2000; | |||||
} | |||||
Synthesiser::~Synthesiser() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
SynthesiserVoice* Synthesiser::getVoice (const int index) const | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
return voices [index]; | |||||
} | |||||
void Synthesiser::clearVoices() | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
voices.clear(); | |||||
} | |||||
void Synthesiser::addVoice (SynthesiserVoice* const newVoice) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
voices.add (newVoice); | |||||
} | |||||
void Synthesiser::removeVoice (const int index) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
voices.remove (index); | |||||
} | |||||
void Synthesiser::clearSounds() | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
sounds.clear(); | |||||
} | |||||
void Synthesiser::addSound (const SynthesiserSound::Ptr& newSound) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
sounds.add (newSound); | |||||
} | |||||
void Synthesiser::removeSound (const int index) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
sounds.remove (index); | |||||
} | |||||
void Synthesiser::setNoteStealingEnabled (const bool shouldStealNotes_) | |||||
{ | |||||
shouldStealNotes = shouldStealNotes_; | |||||
} | |||||
//============================================================================== | |||||
void Synthesiser::setCurrentPlaybackSampleRate (const double newRate) | |||||
{ | |||||
if (sampleRate != newRate) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
allNotesOff (0, false); | |||||
sampleRate = newRate; | |||||
for (int i = voices.size(); --i >= 0;) | |||||
voices.getUnchecked (i)->setCurrentPlaybackSampleRate (newRate); | |||||
} | |||||
} | |||||
void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, | |||||
const MidiBuffer& midiData, | |||||
int startSample, | |||||
int numSamples) | |||||
{ | |||||
// must set the sample rate before using this! | |||||
jassert (sampleRate != 0); | |||||
const ScopedLock sl (lock); | |||||
MidiBuffer::Iterator midiIterator (midiData); | |||||
midiIterator.setNextSamplePosition (startSample); | |||||
MidiMessage m (0xf4, 0.0); | |||||
while (numSamples > 0) | |||||
{ | |||||
int midiEventPos; | |||||
const bool useEvent = midiIterator.getNextEvent (m, midiEventPos) | |||||
&& midiEventPos < startSample + numSamples; | |||||
const int numThisTime = useEvent ? midiEventPos - startSample | |||||
: numSamples; | |||||
if (numThisTime > 0) | |||||
{ | |||||
for (int i = voices.size(); --i >= 0;) | |||||
voices.getUnchecked (i)->renderNextBlock (outputBuffer, startSample, numThisTime); | |||||
} | |||||
if (useEvent) | |||||
handleMidiEvent (m); | |||||
startSample += numThisTime; | |||||
numSamples -= numThisTime; | |||||
} | |||||
} | |||||
void Synthesiser::handleMidiEvent (const MidiMessage& m) | |||||
{ | |||||
if (m.isNoteOn()) | |||||
{ | |||||
noteOn (m.getChannel(), | |||||
m.getNoteNumber(), | |||||
m.getFloatVelocity()); | |||||
} | |||||
else if (m.isNoteOff()) | |||||
{ | |||||
noteOff (m.getChannel(), | |||||
m.getNoteNumber(), | |||||
true); | |||||
} | |||||
else if (m.isAllNotesOff() || m.isAllSoundOff()) | |||||
{ | |||||
allNotesOff (m.getChannel(), true); | |||||
} | |||||
else if (m.isPitchWheel()) | |||||
{ | |||||
const int channel = m.getChannel(); | |||||
const int wheelPos = m.getPitchWheelValue(); | |||||
lastPitchWheelValues [channel - 1] = wheelPos; | |||||
handlePitchWheel (channel, wheelPos); | |||||
} | |||||
else if (m.isController()) | |||||
{ | |||||
handleController (m.getChannel(), | |||||
m.getControllerNumber(), | |||||
m.getControllerValue()); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void Synthesiser::noteOn (const int midiChannel, | |||||
const int midiNoteNumber, | |||||
const float velocity) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
for (int i = sounds.size(); --i >= 0;) | |||||
{ | |||||
SynthesiserSound* const sound = sounds.getUnchecked(i); | |||||
if (sound->appliesToNote (midiNoteNumber) | |||||
&& sound->appliesToChannel (midiChannel)) | |||||
{ | |||||
// If hitting a note that's still ringing, stop it first (it could be | |||||
// still playing because of the sustain or sostenuto pedal). | |||||
for (int j = voices.size(); --j >= 0;) | |||||
{ | |||||
SynthesiserVoice* const voice = voices.getUnchecked (j); | |||||
if (voice->getCurrentlyPlayingNote() == midiNoteNumber | |||||
&& voice->isPlayingChannel (midiChannel)) | |||||
stopVoice (voice, true); | |||||
} | |||||
startVoice (findFreeVoice (sound, shouldStealNotes), | |||||
sound, midiChannel, midiNoteNumber, velocity); | |||||
} | |||||
} | |||||
} | |||||
void Synthesiser::startVoice (SynthesiserVoice* const voice, | |||||
SynthesiserSound* const sound, | |||||
const int midiChannel, | |||||
const int midiNoteNumber, | |||||
const float velocity) | |||||
{ | |||||
if (voice != nullptr && sound != nullptr) | |||||
{ | |||||
if (voice->currentlyPlayingSound != nullptr) | |||||
voice->stopNote (false); | |||||
voice->startNote (midiNoteNumber, velocity, sound, | |||||
lastPitchWheelValues [midiChannel - 1]); | |||||
voice->currentlyPlayingNote = midiNoteNumber; | |||||
voice->noteOnTime = ++lastNoteOnCounter; | |||||
voice->currentlyPlayingSound = sound; | |||||
voice->keyIsDown = true; | |||||
voice->sostenutoPedalDown = false; | |||||
} | |||||
} | |||||
void Synthesiser::stopVoice (SynthesiserVoice* voice, const bool allowTailOff) | |||||
{ | |||||
jassert (voice != nullptr); | |||||
voice->stopNote (allowTailOff); | |||||
// the subclass MUST call clearCurrentNote() if it's not tailing off! RTFM for stopNote()! | |||||
jassert (allowTailOff || (voice->getCurrentlyPlayingNote() < 0 && voice->getCurrentlyPlayingSound() == 0)); | |||||
} | |||||
void Synthesiser::noteOff (const int midiChannel, | |||||
const int midiNoteNumber, | |||||
const bool allowTailOff) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
for (int i = voices.size(); --i >= 0;) | |||||
{ | |||||
SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
if (voice->getCurrentlyPlayingNote() == midiNoteNumber) | |||||
{ | |||||
if (SynthesiserSound* const sound = voice->getCurrentlyPlayingSound()) | |||||
{ | |||||
if (sound->appliesToNote (midiNoteNumber) | |||||
&& sound->appliesToChannel (midiChannel)) | |||||
{ | |||||
voice->keyIsDown = false; | |||||
if (! (sustainPedalsDown [midiChannel] || voice->sostenutoPedalDown)) | |||||
stopVoice (voice, allowTailOff); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
void Synthesiser::allNotesOff (const int midiChannel, const bool allowTailOff) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
for (int i = voices.size(); --i >= 0;) | |||||
{ | |||||
SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | |||||
voice->stopNote (allowTailOff); | |||||
} | |||||
sustainPedalsDown.clear(); | |||||
} | |||||
void Synthesiser::handlePitchWheel (const int midiChannel, const int wheelValue) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
for (int i = voices.size(); --i >= 0;) | |||||
{ | |||||
SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | |||||
voice->pitchWheelMoved (wheelValue); | |||||
} | |||||
} | |||||
void Synthesiser::handleController (const int midiChannel, | |||||
const int controllerNumber, | |||||
const int controllerValue) | |||||
{ | |||||
switch (controllerNumber) | |||||
{ | |||||
case 0x40: handleSustainPedal (midiChannel, controllerValue >= 64); break; | |||||
case 0x42: handleSostenutoPedal (midiChannel, controllerValue >= 64); break; | |||||
case 0x43: handleSoftPedal (midiChannel, controllerValue >= 64); break; | |||||
default: break; | |||||
} | |||||
const ScopedLock sl (lock); | |||||
for (int i = voices.size(); --i >= 0;) | |||||
{ | |||||
SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | |||||
voice->controllerMoved (controllerNumber, controllerValue); | |||||
} | |||||
} | |||||
void Synthesiser::handleSustainPedal (int midiChannel, bool isDown) | |||||
{ | |||||
jassert (midiChannel > 0 && midiChannel <= 16); | |||||
const ScopedLock sl (lock); | |||||
if (isDown) | |||||
{ | |||||
sustainPedalsDown.setBit (midiChannel); | |||||
} | |||||
else | |||||
{ | |||||
for (int i = voices.size(); --i >= 0;) | |||||
{ | |||||
SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
if (voice->isPlayingChannel (midiChannel) && ! voice->keyIsDown) | |||||
stopVoice (voice, true); | |||||
} | |||||
sustainPedalsDown.clearBit (midiChannel); | |||||
} | |||||
} | |||||
void Synthesiser::handleSostenutoPedal (int midiChannel, bool isDown) | |||||
{ | |||||
jassert (midiChannel > 0 && midiChannel <= 16); | |||||
const ScopedLock sl (lock); | |||||
for (int i = voices.size(); --i >= 0;) | |||||
{ | |||||
SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
if (voice->isPlayingChannel (midiChannel)) | |||||
{ | |||||
if (isDown) | |||||
voice->sostenutoPedalDown = true; | |||||
else if (voice->sostenutoPedalDown) | |||||
stopVoice (voice, true); | |||||
} | |||||
} | |||||
} | |||||
void Synthesiser::handleSoftPedal (int midiChannel, bool /*isDown*/) | |||||
{ | |||||
(void) midiChannel; | |||||
jassert (midiChannel > 0 && midiChannel <= 16); | |||||
} | |||||
//============================================================================== | |||||
SynthesiserVoice* Synthesiser::findFreeVoice (SynthesiserSound* soundToPlay, | |||||
const bool stealIfNoneAvailable) const | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
for (int i = voices.size(); --i >= 0;) | |||||
if (voices.getUnchecked (i)->getCurrentlyPlayingNote() < 0 | |||||
&& voices.getUnchecked (i)->canPlaySound (soundToPlay)) | |||||
return voices.getUnchecked (i); | |||||
if (stealIfNoneAvailable) | |||||
{ | |||||
// currently this just steals the one that's been playing the longest, but could be made a bit smarter.. | |||||
SynthesiserVoice* oldest = nullptr; | |||||
for (int i = voices.size(); --i >= 0;) | |||||
{ | |||||
SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
if (voice->canPlaySound (soundToPlay) | |||||
&& (oldest == nullptr || oldest->noteOnTime > voice->noteOnTime)) | |||||
oldest = voice; | |||||
} | |||||
jassert (oldest != nullptr); | |||||
return oldest; | |||||
} | |||||
return nullptr; | |||||
} |
@@ -0,0 +1,494 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_SYNTHESISER_JUCEHEADER__ | |||||
#define __JUCE_SYNTHESISER_JUCEHEADER__ | |||||
#include "../buffers/juce_AudioSampleBuffer.h" | |||||
#include "../midi/juce_MidiBuffer.h" | |||||
//============================================================================== | |||||
/** | |||||
Describes one of the sounds that a Synthesiser can play. | |||||
A synthesiser can contain one or more sounds, and a sound can choose which | |||||
midi notes and channels can trigger it. | |||||
The SynthesiserSound is a passive class that just describes what the sound is - | |||||
the actual audio rendering for a sound is done by a SynthesiserVoice. This allows | |||||
more than one SynthesiserVoice to play the same sound at the same time. | |||||
@see Synthesiser, SynthesiserVoice | |||||
*/ | |||||
class JUCE_API SynthesiserSound : public ReferenceCountedObject | |||||
{ | |||||
protected: | |||||
//============================================================================== | |||||
SynthesiserSound(); | |||||
public: | |||||
/** Destructor. */ | |||||
virtual ~SynthesiserSound(); | |||||
//============================================================================== | |||||
/** Returns true if this sound should be played when a given midi note is pressed. | |||||
The Synthesiser will use this information when deciding which sounds to trigger | |||||
for a given note. | |||||
*/ | |||||
virtual bool appliesToNote (const int midiNoteNumber) = 0; | |||||
/** Returns true if the sound should be triggered by midi events on a given channel. | |||||
The Synthesiser will use this information when deciding which sounds to trigger | |||||
for a given note. | |||||
*/ | |||||
virtual bool appliesToChannel (const int midiChannel) = 0; | |||||
/** | |||||
*/ | |||||
typedef ReferenceCountedObjectPtr <SynthesiserSound> Ptr; | |||||
private: | |||||
//============================================================================== | |||||
JUCE_LEAK_DETECTOR (SynthesiserSound) | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
Represents a voice that a Synthesiser can use to play a SynthesiserSound. | |||||
A voice plays a single sound at a time, and a synthesiser holds an array of | |||||
voices so that it can play polyphonically. | |||||
@see Synthesiser, SynthesiserSound | |||||
*/ | |||||
class JUCE_API SynthesiserVoice | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a voice. */ | |||||
SynthesiserVoice(); | |||||
/** Destructor. */ | |||||
virtual ~SynthesiserVoice(); | |||||
//============================================================================== | |||||
/** Returns the midi note that this voice is currently playing. | |||||
Returns a value less than 0 if no note is playing. | |||||
*/ | |||||
int getCurrentlyPlayingNote() const { return currentlyPlayingNote; } | |||||
/** Returns the sound that this voice is currently playing. | |||||
Returns nullptr if it's not playing. | |||||
*/ | |||||
SynthesiserSound::Ptr getCurrentlyPlayingSound() const { return currentlyPlayingSound; } | |||||
/** Must return true if this voice object is capable of playing the given sound. | |||||
If there are different classes of sound, and different classes of voice, a voice can | |||||
choose which ones it wants to take on. | |||||
A typical implementation of this method may just return true if there's only one type | |||||
of voice and sound, or it might check the type of the sound object passed-in and | |||||
see if it's one that it understands. | |||||
*/ | |||||
virtual bool canPlaySound (SynthesiserSound* sound) = 0; | |||||
/** Called to start a new note. | |||||
This will be called during the rendering callback, so must be fast and thread-safe. | |||||
*/ | |||||
virtual void startNote (const int midiNoteNumber, | |||||
const float velocity, | |||||
SynthesiserSound* sound, | |||||
const int currentPitchWheelPosition) = 0; | |||||
/** Called to stop a note. | |||||
This will be called during the rendering callback, so must be fast and thread-safe. | |||||
If allowTailOff is false or the voice doesn't want to tail-off, then it must stop all | |||||
sound immediately, and must call clearCurrentNote() to reset the state of this voice | |||||
and allow the synth to reassign it another sound. | |||||
If allowTailOff is true and the voice decides to do a tail-off, then it's allowed to | |||||
begin fading out its sound, and it can stop playing until it's finished. As soon as it | |||||
finishes playing (during the rendering callback), it must make sure that it calls | |||||
clearCurrentNote(). | |||||
*/ | |||||
virtual void stopNote (const bool allowTailOff) = 0; | |||||
/** Called to let the voice know that the pitch wheel has been moved. | |||||
This will be called during the rendering callback, so must be fast and thread-safe. | |||||
*/ | |||||
virtual void pitchWheelMoved (const int newValue) = 0; | |||||
/** Called to let the voice know that a midi controller has been moved. | |||||
This will be called during the rendering callback, so must be fast and thread-safe. | |||||
*/ | |||||
virtual void controllerMoved (const int controllerNumber, | |||||
const int newValue) = 0; | |||||
//============================================================================== | |||||
/** Renders the next block of data for this voice. | |||||
The output audio data must be added to the current contents of the buffer provided. | |||||
Only the region of the buffer between startSample and (startSample + numSamples) | |||||
should be altered by this method. | |||||
If the voice is currently silent, it should just return without doing anything. | |||||
If the sound that the voice is playing finishes during the course of this rendered | |||||
block, it must call clearCurrentNote(), to tell the synthesiser that it has finished. | |||||
The size of the blocks that are rendered can change each time it is called, and may | |||||
involve rendering as little as 1 sample at a time. In between rendering callbacks, | |||||
the voice's methods will be called to tell it about note and controller events. | |||||
*/ | |||||
virtual void renderNextBlock (AudioSampleBuffer& outputBuffer, | |||||
int startSample, | |||||
int numSamples) = 0; | |||||
/** Returns true if the voice is currently playing a sound which is mapped to the given | |||||
midi channel. | |||||
If it's not currently playing, this will return false. | |||||
*/ | |||||
bool isPlayingChannel (int midiChannel) const; | |||||
/** Changes the voice's reference sample rate. | |||||
The rate is set so that subclasses know the output rate and can set their pitch | |||||
accordingly. | |||||
This method is called by the synth, and subclasses can access the current rate with | |||||
the currentSampleRate member. | |||||
*/ | |||||
void setCurrentPlaybackSampleRate (double newRate); | |||||
protected: | |||||
//============================================================================== | |||||
/** Returns the current target sample rate at which rendering is being done. | |||||
This is available for subclasses so they can pitch things correctly. | |||||
*/ | |||||
double getSampleRate() const { return currentSampleRate; } | |||||
/** Resets the state of this voice after a sound has finished playing. | |||||
The subclass must call this when it finishes playing a note and becomes available | |||||
to play new ones. | |||||
It must either call it in the stopNote() method, or if the voice is tailing off, | |||||
then it should call it later during the renderNextBlock method, as soon as it | |||||
finishes its tail-off. | |||||
It can also be called at any time during the render callback if the sound happens | |||||
to have finished, e.g. if it's playing a sample and the sample finishes. | |||||
*/ | |||||
void clearCurrentNote(); | |||||
private: | |||||
//============================================================================== | |||||
friend class Synthesiser; | |||||
double currentSampleRate; | |||||
int currentlyPlayingNote; | |||||
uint32 noteOnTime; | |||||
SynthesiserSound::Ptr currentlyPlayingSound; | |||||
bool keyIsDown; // the voice may still be playing when the key is not down (i.e. sustain pedal) | |||||
bool sostenutoPedalDown; | |||||
JUCE_LEAK_DETECTOR (SynthesiserVoice) | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
Base class for a musical device that can play sounds. | |||||
To create a synthesiser, you'll need to create a subclass of SynthesiserSound | |||||
to describe each sound available to your synth, and a subclass of SynthesiserVoice | |||||
which can play back one of these sounds. | |||||
Then you can use the addVoice() and addSound() methods to give the synthesiser a | |||||
set of sounds, and a set of voices it can use to play them. If you only give it | |||||
one voice it will be monophonic - the more voices it has, the more polyphony it'll | |||||
have available. | |||||
Then repeatedly call the renderNextBlock() method to produce the audio. Any midi | |||||
events that go in will be scanned for note on/off messages, and these are used to | |||||
start and stop the voices playing the appropriate sounds. | |||||
While it's playing, you can also cause notes to be triggered by calling the noteOn(), | |||||
noteOff() and other controller methods. | |||||
Before rendering, be sure to call the setCurrentPlaybackSampleRate() to tell it | |||||
what the target playback rate is. This value is passed on to the voices so that | |||||
they can pitch their output correctly. | |||||
*/ | |||||
class JUCE_API Synthesiser | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a new synthesiser. | |||||
You'll need to add some sounds and voices before it'll make any sound.. | |||||
*/ | |||||
Synthesiser(); | |||||
/** Destructor. */ | |||||
virtual ~Synthesiser(); | |||||
//============================================================================== | |||||
/** Deletes all voices. */ | |||||
void clearVoices(); | |||||
/** Returns the number of voices that have been added. */ | |||||
int getNumVoices() const { return voices.size(); } | |||||
/** Returns one of the voices that have been added. */ | |||||
SynthesiserVoice* getVoice (int index) const; | |||||
/** Adds a new voice to the synth. | |||||
All the voices should be the same class of object and are treated equally. | |||||
The object passed in will be managed by the synthesiser, which will delete | |||||
it later on when no longer needed. The caller should not retain a pointer to the | |||||
voice. | |||||
*/ | |||||
void addVoice (SynthesiserVoice* newVoice); | |||||
/** Deletes one of the voices. */ | |||||
void removeVoice (int index); | |||||
//============================================================================== | |||||
/** Deletes all sounds. */ | |||||
void clearSounds(); | |||||
/** Returns the number of sounds that have been added to the synth. */ | |||||
int getNumSounds() const { return sounds.size(); } | |||||
/** Returns one of the sounds. */ | |||||
SynthesiserSound* getSound (int index) const { return sounds [index]; } | |||||
/** Adds a new sound to the synthesiser. | |||||
The object passed in is reference counted, so will be deleted when it is removed | |||||
from the synthesiser, and when no voices are still using it. | |||||
*/ | |||||
void addSound (const SynthesiserSound::Ptr& newSound); | |||||
/** Removes and deletes one of the sounds. */ | |||||
void removeSound (int index); | |||||
//============================================================================== | |||||
/** If set to true, then the synth will try to take over an existing voice if | |||||
it runs out and needs to play another note. | |||||
The value of this boolean is passed into findFreeVoice(), so the result will | |||||
depend on the implementation of this method. | |||||
*/ | |||||
void setNoteStealingEnabled (bool shouldStealNotes); | |||||
/** Returns true if note-stealing is enabled. | |||||
@see setNoteStealingEnabled | |||||
*/ | |||||
bool isNoteStealingEnabled() const { return shouldStealNotes; } | |||||
//============================================================================== | |||||
/** Triggers a note-on event. | |||||
The default method here will find all the sounds that want to be triggered by | |||||
this note/channel. For each sound, it'll try to find a free voice, and use the | |||||
voice to start playing the sound. | |||||
Subclasses might want to override this if they need a more complex algorithm. | |||||
This method will be called automatically according to the midi data passed into | |||||
renderNextBlock(), but may be called explicitly too. | |||||
The midiChannel parameter is the channel, between 1 and 16 inclusive. | |||||
*/ | |||||
virtual void noteOn (int midiChannel, | |||||
int midiNoteNumber, | |||||
float velocity); | |||||
/** Triggers a note-off event. | |||||
This will turn off any voices that are playing a sound for the given note/channel. | |||||
If allowTailOff is true, the voices will be allowed to fade out the notes gracefully | |||||
(if they can do). If this is false, the notes will all be cut off immediately. | |||||
This method will be called automatically according to the midi data passed into | |||||
renderNextBlock(), but may be called explicitly too. | |||||
The midiChannel parameter is the channel, between 1 and 16 inclusive. | |||||
*/ | |||||
virtual void noteOff (int midiChannel, | |||||
int midiNoteNumber, | |||||
bool allowTailOff); | |||||
/** Turns off all notes. | |||||
This will turn off any voices that are playing a sound on the given midi channel. | |||||
If midiChannel is 0 or less, then all voices will be turned off, regardless of | |||||
which channel they're playing. Otherwise it represents a valid midi channel, from | |||||
1 to 16 inclusive. | |||||
If allowTailOff is true, the voices will be allowed to fade out the notes gracefully | |||||
(if they can do). If this is false, the notes will all be cut off immediately. | |||||
This method will be called automatically according to the midi data passed into | |||||
renderNextBlock(), but may be called explicitly too. | |||||
*/ | |||||
virtual void allNotesOff (int midiChannel, | |||||
bool allowTailOff); | |||||
/** Sends a pitch-wheel message. | |||||
This will send a pitch-wheel message to any voices that are playing sounds on | |||||
the given midi channel. | |||||
This method will be called automatically according to the midi data passed into | |||||
renderNextBlock(), but may be called explicitly too. | |||||
@param midiChannel the midi channel, from 1 to 16 inclusive | |||||
@param wheelValue the wheel position, from 0 to 0x3fff, as returned by MidiMessage::getPitchWheelValue() | |||||
*/ | |||||
virtual void handlePitchWheel (int midiChannel, | |||||
int wheelValue); | |||||
/** Sends a midi controller message. | |||||
This will send a midi controller message to any voices that are playing sounds on | |||||
the given midi channel. | |||||
This method will be called automatically according to the midi data passed into | |||||
renderNextBlock(), but may be called explicitly too. | |||||
@param midiChannel the midi channel, from 1 to 16 inclusive | |||||
@param controllerNumber the midi controller type, as returned by MidiMessage::getControllerNumber() | |||||
@param controllerValue the midi controller value, between 0 and 127, as returned by MidiMessage::getControllerValue() | |||||
*/ | |||||
virtual void handleController (int midiChannel, | |||||
int controllerNumber, | |||||
int controllerValue); | |||||
virtual void handleSustainPedal (int midiChannel, bool isDown); | |||||
virtual void handleSostenutoPedal (int midiChannel, bool isDown); | |||||
virtual void handleSoftPedal (int midiChannel, bool isDown); | |||||
//============================================================================== | |||||
/** Tells the synthesiser what the sample rate is for the audio it's being used to | |||||
render. | |||||
This value is propagated to the voices so that they can use it to render the correct | |||||
pitches. | |||||
*/ | |||||
void setCurrentPlaybackSampleRate (double sampleRate); | |||||
/** Creates the next block of audio output. | |||||
This will process the next numSamples of data from all the voices, and add that output | |||||
to the audio block supplied, starting from the offset specified. Note that the | |||||
data will be added to the current contents of the buffer, so you should clear it | |||||
before calling this method if necessary. | |||||
The midi events in the inputMidi buffer are parsed for note and controller events, | |||||
and these are used to trigger the voices. Note that the startSample offset applies | |||||
both to the audio output buffer and the midi input buffer, so any midi events | |||||
with timestamps outside the specified region will be ignored. | |||||
*/ | |||||
void renderNextBlock (AudioSampleBuffer& outputAudio, | |||||
const MidiBuffer& inputMidi, | |||||
int startSample, | |||||
int numSamples); | |||||
protected: | |||||
//============================================================================== | |||||
/** This is used to control access to the rendering callback and the note trigger methods. */ | |||||
CriticalSection lock; | |||||
OwnedArray <SynthesiserVoice> voices; | |||||
ReferenceCountedArray <SynthesiserSound> sounds; | |||||
/** The last pitch-wheel values for each midi channel. */ | |||||
int lastPitchWheelValues [16]; | |||||
/** Searches through the voices to find one that's not currently playing, and which | |||||
can play the given sound. | |||||
Returns nullptr if all voices are busy and stealing isn't enabled. | |||||
This can be overridden to implement custom voice-stealing algorithms. | |||||
*/ | |||||
virtual SynthesiserVoice* findFreeVoice (SynthesiserSound* soundToPlay, | |||||
const bool stealIfNoneAvailable) const; | |||||
/** Starts a specified voice playing a particular sound. | |||||
You'll probably never need to call this, it's used internally by noteOn(), but | |||||
may be needed by subclasses for custom behaviours. | |||||
*/ | |||||
void startVoice (SynthesiserVoice* voice, | |||||
SynthesiserSound* sound, | |||||
int midiChannel, | |||||
int midiNoteNumber, | |||||
float velocity); | |||||
private: | |||||
//============================================================================== | |||||
double sampleRate; | |||||
uint32 lastNoteOnCounter; | |||||
bool shouldStealNotes; | |||||
BigInteger sustainPedalsDown; | |||||
void handleMidiEvent (const MidiMessage& m); | |||||
void stopVoice (SynthesiserVoice* voice, bool allowTailOff); | |||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE | |||||
// Note the new parameters for this method. | |||||
virtual int findFreeVoice (const bool) const { return 0; } | |||||
#endif | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Synthesiser) | |||||
}; | |||||
#endif // __JUCE_SYNTHESISER_JUCEHEADER__ |
@@ -0,0 +1,170 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_AUDIOCDBURNER_JUCEHEADER__ | |||||
#define __JUCE_AUDIOCDBURNER_JUCEHEADER__ | |||||
#if JUCE_USE_CDBURNER || DOXYGEN | |||||
//============================================================================== | |||||
/** | |||||
*/ | |||||
class AudioCDBurner : public ChangeBroadcaster | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Returns a list of available optical drives. | |||||
Use openDevice() to open one of the items from this list. | |||||
*/ | |||||
static StringArray findAvailableDevices(); | |||||
/** Tries to open one of the optical drives. | |||||
The deviceIndex is an index into the array returned by findAvailableDevices(). | |||||
*/ | |||||
static AudioCDBurner* openDevice (const int deviceIndex); | |||||
/** Destructor. */ | |||||
~AudioCDBurner(); | |||||
//============================================================================== | |||||
enum DiskState | |||||
{ | |||||
unknown, /**< An error condition, if the device isn't responding. */ | |||||
trayOpen, /**< The drive is currently open. Note that a slot-loading drive | |||||
may seem to be permanently open. */ | |||||
noDisc, /**< The drive has no disk in it. */ | |||||
writableDiskPresent, /**< The drive contains a writeable disk. */ | |||||
readOnlyDiskPresent /**< The drive contains a read-only disk. */ | |||||
}; | |||||
/** Returns the current status of the device. | |||||
To get informed when the drive's status changes, attach a ChangeListener to | |||||
the AudioCDBurner. | |||||
*/ | |||||
DiskState getDiskState() const; | |||||
/** Returns true if there's a writable disk in the drive. */ | |||||
bool isDiskPresent() const; | |||||
/** Sends an eject signal to the drive. | |||||
The eject will happen asynchronously, so you can use getDiskState() and | |||||
waitUntilStateChange() to monitor its progress. | |||||
*/ | |||||
bool openTray(); | |||||
/** Blocks the current thread until the drive's state changes, or until the timeout expires. | |||||
@returns the device's new state | |||||
*/ | |||||
DiskState waitUntilStateChange (int timeOutMilliseconds); | |||||
//============================================================================== | |||||
/** Returns the set of possible write speeds that the device can handle. | |||||
These are as a multiple of 'normal' speed, so e.g. '24x' returns 24, etc. | |||||
Note that if there's no media present in the drive, this value may be unavailable! | |||||
@see setWriteSpeed, getWriteSpeed | |||||
*/ | |||||
Array<int> getAvailableWriteSpeeds() const; | |||||
//============================================================================== | |||||
/** Tries to enable or disable buffer underrun safety on devices that support it. | |||||
@returns true if it's now enabled. If the device doesn't support it, this | |||||
will always return false. | |||||
*/ | |||||
bool setBufferUnderrunProtection (bool shouldBeEnabled); | |||||
//============================================================================== | |||||
/** Returns the number of free blocks on the disk. | |||||
There are 75 blocks per second, at 44100Hz. | |||||
*/ | |||||
int getNumAvailableAudioBlocks() const; | |||||
/** Adds a track to be written. | |||||
The source passed-in here will be kept by this object, and it will | |||||
be used and deleted at some point in the future, either during the | |||||
burn() method or when this AudioCDBurner object is deleted. Your caller | |||||
method shouldn't keep a reference to it or use it again after passing | |||||
it in here. | |||||
*/ | |||||
bool addAudioTrack (AudioSource* source, int numSamples); | |||||
//============================================================================== | |||||
/** Receives progress callbacks during a cd-burn operation. | |||||
@see AudioCDBurner::burn() | |||||
*/ | |||||
class BurnProgressListener | |||||
{ | |||||
public: | |||||
BurnProgressListener() noexcept {} | |||||
virtual ~BurnProgressListener() {} | |||||
/** Called at intervals to report on the progress of the AudioCDBurner. | |||||
To cancel the burn, return true from this method. | |||||
*/ | |||||
virtual bool audioCDBurnProgress (float proportionComplete) = 0; | |||||
}; | |||||
/** Runs the burn process. | |||||
This method will block until the operation is complete. | |||||
@param listener the object to receive callbacks about progress | |||||
@param ejectDiscAfterwards whether to eject the disk after the burn completes | |||||
@param performFakeBurnForTesting if true, no data will actually be written to the disk | |||||
@param writeSpeed one of the write speeds from getAvailableWriteSpeeds(), or | |||||
0 or less to mean the fastest speed. | |||||
*/ | |||||
String burn (BurnProgressListener* listener, | |||||
bool ejectDiscAfterwards, | |||||
bool performFakeBurnForTesting, | |||||
int writeSpeed); | |||||
/** If a burn operation is currently in progress, this tells it to stop | |||||
as soon as possible. | |||||
It's also possible to stop the burn process by returning true from | |||||
BurnProgressListener::audioCDBurnProgress() | |||||
*/ | |||||
void abortBurn(); | |||||
private: | |||||
//============================================================================== | |||||
AudioCDBurner (const int deviceIndex); | |||||
class Pimpl; | |||||
friend class ScopedPointer<Pimpl>; | |||||
ScopedPointer<Pimpl> pimpl; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioCDBurner) | |||||
}; | |||||
#endif | |||||
#endif // __JUCE_AUDIOCDBURNER_JUCEHEADER__ |
@@ -0,0 +1,58 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#if JUCE_USE_CDREADER | |||||
int AudioCDReader::getNumTracks() const | |||||
{ | |||||
return trackStartSamples.size() - 1; | |||||
} | |||||
int AudioCDReader::getPositionOfTrackStart (int trackNum) const | |||||
{ | |||||
return trackStartSamples [trackNum]; | |||||
} | |||||
const Array<int>& AudioCDReader::getTrackOffsets() const | |||||
{ | |||||
return trackStartSamples; | |||||
} | |||||
int AudioCDReader::getCDDBId() | |||||
{ | |||||
int checksum = 0; | |||||
const int numTracks = getNumTracks(); | |||||
for (int i = 0; i < numTracks; ++i) | |||||
for (int offset = (trackStartSamples.getUnchecked(i) + 88200) / 44100; offset > 0; offset /= 10) | |||||
checksum += offset % 10; | |||||
const int length = (trackStartSamples.getLast() - trackStartSamples.getFirst()) / 44100; | |||||
// CCLLLLTT: checksum, length, tracks | |||||
return ((checksum & 0xff) << 24) | (length << 8) | numTracks; | |||||
} | |||||
#endif |
@@ -0,0 +1,175 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_AUDIOCDREADER_JUCEHEADER__ | |||||
#define __JUCE_AUDIOCDREADER_JUCEHEADER__ | |||||
#if JUCE_USE_CDREADER || DOXYGEN | |||||
//============================================================================== | |||||
/** | |||||
A type of AudioFormatReader that reads from an audio CD. | |||||
One of these can be used to read a CD as if it's one big audio stream. Use the | |||||
getPositionOfTrackStart() method to find where the individual tracks are | |||||
within the stream. | |||||
@see AudioFormatReader | |||||
*/ | |||||
class JUCE_API AudioCDReader : public AudioFormatReader | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Returns a list of names of Audio CDs currently available for reading. | |||||
If there's a CD drive but no CD in it, this might return an empty list, or | |||||
possibly a device that can be opened but which has no tracks, depending | |||||
on the platform. | |||||
@see createReaderForCD | |||||
*/ | |||||
static StringArray getAvailableCDNames(); | |||||
/** Tries to create an AudioFormatReader that can read from an Audio CD. | |||||
@param index the index of one of the available CDs - use getAvailableCDNames() | |||||
to find out how many there are. | |||||
@returns a new AudioCDReader object, or nullptr if it couldn't be created. The | |||||
caller will be responsible for deleting the object returned. | |||||
*/ | |||||
static AudioCDReader* createReaderForCD (const int index); | |||||
//============================================================================== | |||||
/** Destructor. */ | |||||
~AudioCDReader(); | |||||
/** Implementation of the AudioFormatReader method. */ | |||||
bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | |||||
int64 startSampleInFile, int numSamples); | |||||
/** Checks whether the CD has been removed from the drive. */ | |||||
bool isCDStillPresent() const; | |||||
/** Returns the total number of tracks (audio + data). */ | |||||
int getNumTracks() const; | |||||
/** Finds the sample offset of the start of a track. | |||||
@param trackNum the track number, where trackNum = 0 is the first track | |||||
and trackNum = getNumTracks() means the end of the CD. | |||||
*/ | |||||
int getPositionOfTrackStart (int trackNum) const; | |||||
/** Returns true if a given track is an audio track. | |||||
@param trackNum the track number, where 0 is the first track. | |||||
*/ | |||||
bool isTrackAudio (int trackNum) const; | |||||
/** Returns an array of sample offsets for the start of each track, followed by | |||||
the sample position of the end of the CD. | |||||
*/ | |||||
const Array<int>& getTrackOffsets() const; | |||||
/** Refreshes the object's table of contents. | |||||
If the disc has been ejected and a different one put in since this | |||||
object was created, this will cause it to update its idea of how many tracks | |||||
there are, etc. | |||||
*/ | |||||
void refreshTrackLengths(); | |||||
/** Enables scanning for indexes within tracks. | |||||
@see getLastIndex | |||||
*/ | |||||
void enableIndexScanning (bool enabled); | |||||
/** Returns the index number found during the last read() call. | |||||
Index scanning is turned off by default - turn it on with enableIndexScanning(). | |||||
Then when the read() method is called, if it comes across an index within that | |||||
block, the index number is stored and returned by this method. | |||||
Some devices might not support indexes, of course. | |||||
(If you don't know what CD indexes are, it's unlikely you'll ever need them). | |||||
@see enableIndexScanning | |||||
*/ | |||||
int getLastIndex() const; | |||||
/** Scans a track to find the position of any indexes within it. | |||||
@param trackNumber the track to look in, where 0 is the first track on the disc | |||||
@returns an array of sample positions of any index points found (not including | |||||
the index that marks the start of the track) | |||||
*/ | |||||
Array<int> findIndexesInTrack (const int trackNumber); | |||||
/** Returns the CDDB id number for the CD. | |||||
It's not a great way of identifying a disc, but it's traditional. | |||||
*/ | |||||
int getCDDBId(); | |||||
/** Tries to eject the disk. | |||||
Ejecting the disk might not actually be possible, e.g. if some other process is using it. | |||||
*/ | |||||
void ejectDisk(); | |||||
//============================================================================== | |||||
enum | |||||
{ | |||||
framesPerSecond = 75, | |||||
samplesPerFrame = 44100 / framesPerSecond | |||||
}; | |||||
private: | |||||
//============================================================================== | |||||
Array<int> trackStartSamples; | |||||
#if JUCE_MAC | |||||
File volumeDir; | |||||
Array<File> tracks; | |||||
int currentReaderTrack; | |||||
ScopedPointer <AudioFormatReader> reader; | |||||
AudioCDReader (const File& volume); | |||||
#elif JUCE_WINDOWS | |||||
bool audioTracks [100]; | |||||
void* handle; | |||||
MemoryBlock buffer; | |||||
bool indexingEnabled; | |||||
int lastIndex, firstFrameInBuffer, samplesInBuffer; | |||||
AudioCDReader (void* handle); | |||||
int getIndexAt (int samplePos); | |||||
#elif JUCE_LINUX | |||||
AudioCDReader(); | |||||
#endif | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioCDReader) | |||||
}; | |||||
#endif | |||||
#endif // __JUCE_AUDIOCDREADER_JUCEHEADER__ |
@@ -0,0 +1,953 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
AudioDeviceManager::AudioDeviceSetup::AudioDeviceSetup() | |||||
: sampleRate (0), | |||||
bufferSize (0), | |||||
useDefaultInputChannels (true), | |||||
useDefaultOutputChannels (true) | |||||
{ | |||||
} | |||||
bool AudioDeviceManager::AudioDeviceSetup::operator== (const AudioDeviceManager::AudioDeviceSetup& other) const | |||||
{ | |||||
return outputDeviceName == other.outputDeviceName | |||||
&& inputDeviceName == other.inputDeviceName | |||||
&& sampleRate == other.sampleRate | |||||
&& bufferSize == other.bufferSize | |||||
&& inputChannels == other.inputChannels | |||||
&& useDefaultInputChannels == other.useDefaultInputChannels | |||||
&& outputChannels == other.outputChannels | |||||
&& useDefaultOutputChannels == other.useDefaultOutputChannels; | |||||
} | |||||
//============================================================================== | |||||
class AudioDeviceManager::CallbackHandler : public AudioIODeviceCallback, | |||||
public MidiInputCallback, | |||||
public AudioIODeviceType::Listener | |||||
{ | |||||
public: | |||||
CallbackHandler (AudioDeviceManager& adm) noexcept : owner (adm) {} | |||||
private: | |||||
void audioDeviceIOCallback (const float** ins, int numIns, float** outs, int numOuts, int numSamples) | |||||
{ | |||||
owner.audioDeviceIOCallbackInt (ins, numIns, outs, numOuts, numSamples); | |||||
} | |||||
void audioDeviceAboutToStart (AudioIODevice* device) | |||||
{ | |||||
owner.audioDeviceAboutToStartInt (device); | |||||
} | |||||
void audioDeviceStopped() | |||||
{ | |||||
owner.audioDeviceStoppedInt(); | |||||
} | |||||
void audioDeviceError (const String& message) | |||||
{ | |||||
owner.audioDeviceErrorInt (message); | |||||
} | |||||
void handleIncomingMidiMessage (MidiInput* source, const MidiMessage& message) | |||||
{ | |||||
owner.handleIncomingMidiMessageInt (source, message); | |||||
} | |||||
void audioDeviceListChanged() | |||||
{ | |||||
owner.audioDeviceListChanged(); | |||||
} | |||||
AudioDeviceManager& owner; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CallbackHandler) | |||||
}; | |||||
//============================================================================== | |||||
AudioDeviceManager::AudioDeviceManager() | |||||
: numInputChansNeeded (0), | |||||
numOutputChansNeeded (2), | |||||
listNeedsScanning (true), | |||||
useInputNames (false), | |||||
inputLevelMeasurementEnabledCount (0), | |||||
inputLevel (0), | |||||
tempBuffer (2, 2), | |||||
cpuUsageMs (0), | |||||
timeToCpuScale (0) | |||||
{ | |||||
callbackHandler = new CallbackHandler (*this); | |||||
} | |||||
AudioDeviceManager::~AudioDeviceManager() | |||||
{ | |||||
currentAudioDevice = nullptr; | |||||
defaultMidiOutput = nullptr; | |||||
} | |||||
//============================================================================== | |||||
void AudioDeviceManager::createDeviceTypesIfNeeded() | |||||
{ | |||||
if (availableDeviceTypes.size() == 0) | |||||
{ | |||||
createAudioDeviceTypes (availableDeviceTypes); | |||||
while (lastDeviceTypeConfigs.size() < availableDeviceTypes.size()) | |||||
lastDeviceTypeConfigs.add (new AudioDeviceSetup()); | |||||
if (availableDeviceTypes.size() > 0) | |||||
currentDeviceType = availableDeviceTypes.getUnchecked(0)->getTypeName(); | |||||
for (int i = 0; i < availableDeviceTypes.size(); ++i) | |||||
availableDeviceTypes.getUnchecked(i)->addListener (callbackHandler); | |||||
} | |||||
} | |||||
const OwnedArray <AudioIODeviceType>& AudioDeviceManager::getAvailableDeviceTypes() | |||||
{ | |||||
scanDevicesIfNeeded(); | |||||
return availableDeviceTypes; | |||||
} | |||||
void AudioDeviceManager::audioDeviceListChanged() | |||||
{ | |||||
sendChangeMessage(); | |||||
} | |||||
//============================================================================== | |||||
static void addIfNotNull (OwnedArray <AudioIODeviceType>& list, AudioIODeviceType* const device) | |||||
{ | |||||
if (device != nullptr) | |||||
list.add (device); | |||||
} | |||||
void AudioDeviceManager::createAudioDeviceTypes (OwnedArray <AudioIODeviceType>& list) | |||||
{ | |||||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_WASAPI()); | |||||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_DirectSound()); | |||||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_ASIO()); | |||||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_CoreAudio()); | |||||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_iOSAudio()); | |||||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_ALSA()); | |||||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_JACK()); | |||||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_OpenSLES()); | |||||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_Android()); | |||||
} | |||||
void AudioDeviceManager::addAudioDeviceType (AudioIODeviceType* newDeviceType) | |||||
{ | |||||
jassert (newDeviceType != nullptr); | |||||
availableDeviceTypes.add (newDeviceType); | |||||
} | |||||
//============================================================================== | |||||
String AudioDeviceManager::initialise (const int numInputChannelsNeeded, | |||||
const int numOutputChannelsNeeded, | |||||
const XmlElement* const e, | |||||
const bool selectDefaultDeviceOnFailure, | |||||
const String& preferredDefaultDeviceName, | |||||
const AudioDeviceSetup* preferredSetupOptions) | |||||
{ | |||||
scanDevicesIfNeeded(); | |||||
numInputChansNeeded = numInputChannelsNeeded; | |||||
numOutputChansNeeded = numOutputChannelsNeeded; | |||||
if (e != nullptr && e->hasTagName ("DEVICESETUP")) | |||||
{ | |||||
lastExplicitSettings = new XmlElement (*e); | |||||
String error; | |||||
AudioDeviceSetup setup; | |||||
if (preferredSetupOptions != nullptr) | |||||
setup = *preferredSetupOptions; | |||||
if (e->getStringAttribute ("audioDeviceName").isNotEmpty()) | |||||
{ | |||||
setup.inputDeviceName = setup.outputDeviceName | |||||
= e->getStringAttribute ("audioDeviceName"); | |||||
} | |||||
else | |||||
{ | |||||
setup.inputDeviceName = e->getStringAttribute ("audioInputDeviceName"); | |||||
setup.outputDeviceName = e->getStringAttribute ("audioOutputDeviceName"); | |||||
} | |||||
currentDeviceType = e->getStringAttribute ("deviceType"); | |||||
if (findType (currentDeviceType) == nullptr) | |||||
{ | |||||
if (AudioIODeviceType* const type = findType (setup.inputDeviceName, setup.outputDeviceName)) | |||||
currentDeviceType = type->getTypeName(); | |||||
else if (availableDeviceTypes.size() > 0) | |||||
currentDeviceType = availableDeviceTypes.getUnchecked(0)->getTypeName(); | |||||
} | |||||
setup.bufferSize = e->getIntAttribute ("audioDeviceBufferSize"); | |||||
setup.sampleRate = e->getDoubleAttribute ("audioDeviceRate"); | |||||
setup.inputChannels .parseString (e->getStringAttribute ("audioDeviceInChans", "11"), 2); | |||||
setup.outputChannels.parseString (e->getStringAttribute ("audioDeviceOutChans", "11"), 2); | |||||
setup.useDefaultInputChannels = ! e->hasAttribute ("audioDeviceInChans"); | |||||
setup.useDefaultOutputChannels = ! e->hasAttribute ("audioDeviceOutChans"); | |||||
error = setAudioDeviceSetup (setup, true); | |||||
midiInsFromXml.clear(); | |||||
forEachXmlChildElementWithTagName (*e, c, "MIDIINPUT") | |||||
midiInsFromXml.add (c->getStringAttribute ("name")); | |||||
const StringArray allMidiIns (MidiInput::getDevices()); | |||||
for (int i = allMidiIns.size(); --i >= 0;) | |||||
setMidiInputEnabled (allMidiIns[i], midiInsFromXml.contains (allMidiIns[i])); | |||||
if (error.isNotEmpty() && selectDefaultDeviceOnFailure) | |||||
error = initialise (numInputChannelsNeeded, numOutputChannelsNeeded, 0, | |||||
false, preferredDefaultDeviceName); | |||||
setDefaultMidiOutput (e->getStringAttribute ("defaultMidiOutput")); | |||||
return error; | |||||
} | |||||
else | |||||
{ | |||||
AudioDeviceSetup setup; | |||||
if (preferredSetupOptions != nullptr) | |||||
{ | |||||
setup = *preferredSetupOptions; | |||||
} | |||||
else if (preferredDefaultDeviceName.isNotEmpty()) | |||||
{ | |||||
for (int j = availableDeviceTypes.size(); --j >= 0;) | |||||
{ | |||||
AudioIODeviceType* const type = availableDeviceTypes.getUnchecked(j); | |||||
const StringArray outs (type->getDeviceNames (false)); | |||||
for (int i = 0; i < outs.size(); ++i) | |||||
{ | |||||
if (outs[i].matchesWildcard (preferredDefaultDeviceName, true)) | |||||
{ | |||||
setup.outputDeviceName = outs[i]; | |||||
break; | |||||
} | |||||
} | |||||
const StringArray ins (type->getDeviceNames (true)); | |||||
for (int i = 0; i < ins.size(); ++i) | |||||
{ | |||||
if (ins[i].matchesWildcard (preferredDefaultDeviceName, true)) | |||||
{ | |||||
setup.inputDeviceName = ins[i]; | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
insertDefaultDeviceNames (setup); | |||||
return setAudioDeviceSetup (setup, false); | |||||
} | |||||
} | |||||
void AudioDeviceManager::insertDefaultDeviceNames (AudioDeviceSetup& setup) const | |||||
{ | |||||
if (AudioIODeviceType* type = getCurrentDeviceTypeObject()) | |||||
{ | |||||
if (setup.outputDeviceName.isEmpty()) | |||||
setup.outputDeviceName = type->getDeviceNames (false) [type->getDefaultDeviceIndex (false)]; | |||||
if (setup.inputDeviceName.isEmpty()) | |||||
setup.inputDeviceName = type->getDeviceNames (true) [type->getDefaultDeviceIndex (true)]; | |||||
} | |||||
} | |||||
XmlElement* AudioDeviceManager::createStateXml() const | |||||
{ | |||||
return lastExplicitSettings.createCopy(); | |||||
} | |||||
//============================================================================== | |||||
void AudioDeviceManager::scanDevicesIfNeeded() | |||||
{ | |||||
if (listNeedsScanning) | |||||
{ | |||||
listNeedsScanning = false; | |||||
createDeviceTypesIfNeeded(); | |||||
for (int i = availableDeviceTypes.size(); --i >= 0;) | |||||
availableDeviceTypes.getUnchecked(i)->scanForDevices(); | |||||
} | |||||
} | |||||
AudioIODeviceType* AudioDeviceManager::findType (const String& typeName) | |||||
{ | |||||
scanDevicesIfNeeded(); | |||||
for (int i = availableDeviceTypes.size(); --i >= 0;) | |||||
if (availableDeviceTypes.getUnchecked(i)->getTypeName() == typeName) | |||||
return availableDeviceTypes.getUnchecked(i); | |||||
return nullptr; | |||||
} | |||||
AudioIODeviceType* AudioDeviceManager::findType (const String& inputName, const String& outputName) | |||||
{ | |||||
scanDevicesIfNeeded(); | |||||
for (int i = availableDeviceTypes.size(); --i >= 0;) | |||||
{ | |||||
AudioIODeviceType* const type = availableDeviceTypes.getUnchecked(i); | |||||
if ((inputName.isNotEmpty() && type->getDeviceNames (true).contains (inputName, true)) | |||||
|| (outputName.isNotEmpty() && type->getDeviceNames (false).contains (outputName, true))) | |||||
{ | |||||
return type; | |||||
} | |||||
} | |||||
return nullptr; | |||||
} | |||||
void AudioDeviceManager::getAudioDeviceSetup (AudioDeviceSetup& setup) | |||||
{ | |||||
setup = currentSetup; | |||||
} | |||||
void AudioDeviceManager::deleteCurrentDevice() | |||||
{ | |||||
currentAudioDevice = nullptr; | |||||
currentSetup.inputDeviceName = String::empty; | |||||
currentSetup.outputDeviceName = String::empty; | |||||
} | |||||
void AudioDeviceManager::setCurrentAudioDeviceType (const String& type, | |||||
const bool treatAsChosenDevice) | |||||
{ | |||||
for (int i = 0; i < availableDeviceTypes.size(); ++i) | |||||
{ | |||||
if (availableDeviceTypes.getUnchecked(i)->getTypeName() == type | |||||
&& currentDeviceType != type) | |||||
{ | |||||
currentDeviceType = type; | |||||
AudioDeviceSetup s (*lastDeviceTypeConfigs.getUnchecked(i)); | |||||
insertDefaultDeviceNames (s); | |||||
setAudioDeviceSetup (s, treatAsChosenDevice); | |||||
sendChangeMessage(); | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
AudioIODeviceType* AudioDeviceManager::getCurrentDeviceTypeObject() const | |||||
{ | |||||
for (int i = 0; i < availableDeviceTypes.size(); ++i) | |||||
if (availableDeviceTypes[i]->getTypeName() == currentDeviceType) | |||||
return availableDeviceTypes[i]; | |||||
return availableDeviceTypes[0]; | |||||
} | |||||
String AudioDeviceManager::setAudioDeviceSetup (const AudioDeviceSetup& newSetup, | |||||
const bool treatAsChosenDevice) | |||||
{ | |||||
jassert (&newSetup != ¤tSetup); // this will have no effect | |||||
if (newSetup == currentSetup && currentAudioDevice != nullptr) | |||||
return String::empty; | |||||
if (! (newSetup == currentSetup)) | |||||
sendChangeMessage(); | |||||
stopDevice(); | |||||
const String newInputDeviceName (numInputChansNeeded == 0 ? String::empty : newSetup.inputDeviceName); | |||||
const String newOutputDeviceName (numOutputChansNeeded == 0 ? String::empty : newSetup.outputDeviceName); | |||||
String error; | |||||
AudioIODeviceType* type = getCurrentDeviceTypeObject(); | |||||
if (type == nullptr || (newInputDeviceName.isEmpty() && newOutputDeviceName.isEmpty())) | |||||
{ | |||||
deleteCurrentDevice(); | |||||
if (treatAsChosenDevice) | |||||
updateXml(); | |||||
return String::empty; | |||||
} | |||||
if (currentSetup.inputDeviceName != newInputDeviceName | |||||
|| currentSetup.outputDeviceName != newOutputDeviceName | |||||
|| currentAudioDevice == nullptr) | |||||
{ | |||||
deleteCurrentDevice(); | |||||
scanDevicesIfNeeded(); | |||||
if (newOutputDeviceName.isNotEmpty() | |||||
&& ! type->getDeviceNames (false).contains (newOutputDeviceName)) | |||||
{ | |||||
return "No such device: " + newOutputDeviceName; | |||||
} | |||||
if (newInputDeviceName.isNotEmpty() | |||||
&& ! type->getDeviceNames (true).contains (newInputDeviceName)) | |||||
{ | |||||
return "No such device: " + newInputDeviceName; | |||||
} | |||||
currentAudioDevice = type->createDevice (newOutputDeviceName, newInputDeviceName); | |||||
if (currentAudioDevice == nullptr) | |||||
error = "Can't open the audio device!\n\nThis may be because another application is currently using the same device - if so, you should close any other applications and try again!"; | |||||
else | |||||
error = currentAudioDevice->getLastError(); | |||||
if (error.isNotEmpty()) | |||||
{ | |||||
deleteCurrentDevice(); | |||||
return error; | |||||
} | |||||
if (newSetup.useDefaultInputChannels) | |||||
{ | |||||
inputChannels.clear(); | |||||
inputChannels.setRange (0, numInputChansNeeded, true); | |||||
} | |||||
if (newSetup.useDefaultOutputChannels) | |||||
{ | |||||
outputChannels.clear(); | |||||
outputChannels.setRange (0, numOutputChansNeeded, true); | |||||
} | |||||
if (newInputDeviceName.isEmpty()) inputChannels.clear(); | |||||
if (newOutputDeviceName.isEmpty()) outputChannels.clear(); | |||||
} | |||||
if (! newSetup.useDefaultInputChannels) inputChannels = newSetup.inputChannels; | |||||
if (! newSetup.useDefaultOutputChannels) outputChannels = newSetup.outputChannels; | |||||
currentSetup = newSetup; | |||||
currentSetup.sampleRate = chooseBestSampleRate (newSetup.sampleRate); | |||||
currentSetup.bufferSize = chooseBestBufferSize (newSetup.bufferSize); | |||||
error = currentAudioDevice->open (inputChannels, | |||||
outputChannels, | |||||
currentSetup.sampleRate, | |||||
currentSetup.bufferSize); | |||||
if (error.isEmpty()) | |||||
{ | |||||
currentDeviceType = currentAudioDevice->getTypeName(); | |||||
currentAudioDevice->start (callbackHandler); | |||||
currentSetup.sampleRate = currentAudioDevice->getCurrentSampleRate(); | |||||
currentSetup.bufferSize = currentAudioDevice->getCurrentBufferSizeSamples(); | |||||
currentSetup.inputChannels = currentAudioDevice->getActiveInputChannels(); | |||||
currentSetup.outputChannels = currentAudioDevice->getActiveOutputChannels(); | |||||
for (int i = 0; i < availableDeviceTypes.size(); ++i) | |||||
if (availableDeviceTypes.getUnchecked (i)->getTypeName() == currentDeviceType) | |||||
*(lastDeviceTypeConfigs.getUnchecked (i)) = currentSetup; | |||||
if (treatAsChosenDevice) | |||||
updateXml(); | |||||
} | |||||
else | |||||
{ | |||||
deleteCurrentDevice(); | |||||
} | |||||
return error; | |||||
} | |||||
double AudioDeviceManager::chooseBestSampleRate (double rate) const | |||||
{ | |||||
jassert (currentAudioDevice != nullptr); | |||||
if (rate > 0) | |||||
for (int i = currentAudioDevice->getNumSampleRates(); --i >= 0;) | |||||
if (currentAudioDevice->getSampleRate (i) == rate) | |||||
return rate; | |||||
double lowestAbove44 = 0.0; | |||||
for (int i = currentAudioDevice->getNumSampleRates(); --i >= 0;) | |||||
{ | |||||
const double sr = currentAudioDevice->getSampleRate (i); | |||||
if (sr >= 44100.0 && (lowestAbove44 < 1.0 || sr < lowestAbove44)) | |||||
lowestAbove44 = sr; | |||||
} | |||||
if (lowestAbove44 > 0.0) | |||||
return lowestAbove44; | |||||
return currentAudioDevice->getSampleRate (0); | |||||
} | |||||
int AudioDeviceManager::chooseBestBufferSize (int bufferSize) const | |||||
{ | |||||
jassert (currentAudioDevice != nullptr); | |||||
if (bufferSize > 0) | |||||
for (int i = currentAudioDevice->getNumBufferSizesAvailable(); --i >= 0;) | |||||
if (currentAudioDevice->getBufferSizeSamples(i) == bufferSize) | |||||
return bufferSize; | |||||
return currentAudioDevice->getDefaultBufferSize(); | |||||
} | |||||
void AudioDeviceManager::stopDevice() | |||||
{ | |||||
if (currentAudioDevice != nullptr) | |||||
currentAudioDevice->stop(); | |||||
testSound = nullptr; | |||||
} | |||||
void AudioDeviceManager::closeAudioDevice() | |||||
{ | |||||
stopDevice(); | |||||
currentAudioDevice = nullptr; | |||||
} | |||||
void AudioDeviceManager::restartLastAudioDevice() | |||||
{ | |||||
if (currentAudioDevice == nullptr) | |||||
{ | |||||
if (currentSetup.inputDeviceName.isEmpty() | |||||
&& currentSetup.outputDeviceName.isEmpty()) | |||||
{ | |||||
// This method will only reload the last device that was running | |||||
// before closeAudioDevice() was called - you need to actually open | |||||
// one first, with setAudioDevice(). | |||||
jassertfalse; | |||||
return; | |||||
} | |||||
AudioDeviceSetup s (currentSetup); | |||||
setAudioDeviceSetup (s, false); | |||||
} | |||||
} | |||||
void AudioDeviceManager::updateXml() | |||||
{ | |||||
lastExplicitSettings = new XmlElement ("DEVICESETUP"); | |||||
lastExplicitSettings->setAttribute ("deviceType", currentDeviceType); | |||||
lastExplicitSettings->setAttribute ("audioOutputDeviceName", currentSetup.outputDeviceName); | |||||
lastExplicitSettings->setAttribute ("audioInputDeviceName", currentSetup.inputDeviceName); | |||||
if (currentAudioDevice != nullptr) | |||||
{ | |||||
lastExplicitSettings->setAttribute ("audioDeviceRate", currentAudioDevice->getCurrentSampleRate()); | |||||
if (currentAudioDevice->getDefaultBufferSize() != currentAudioDevice->getCurrentBufferSizeSamples()) | |||||
lastExplicitSettings->setAttribute ("audioDeviceBufferSize", currentAudioDevice->getCurrentBufferSizeSamples()); | |||||
if (! currentSetup.useDefaultInputChannels) | |||||
lastExplicitSettings->setAttribute ("audioDeviceInChans", currentSetup.inputChannels.toString (2)); | |||||
if (! currentSetup.useDefaultOutputChannels) | |||||
lastExplicitSettings->setAttribute ("audioDeviceOutChans", currentSetup.outputChannels.toString (2)); | |||||
} | |||||
for (int i = 0; i < enabledMidiInputs.size(); ++i) | |||||
lastExplicitSettings->createNewChildElement ("MIDIINPUT") | |||||
->setAttribute ("name", enabledMidiInputs[i]->getName()); | |||||
if (midiInsFromXml.size() > 0) | |||||
{ | |||||
// Add any midi devices that have been enabled before, but which aren't currently | |||||
// open because the device has been disconnected. | |||||
const StringArray availableMidiDevices (MidiInput::getDevices()); | |||||
for (int i = 0; i < midiInsFromXml.size(); ++i) | |||||
{ | |||||
if (! availableMidiDevices.contains (midiInsFromXml[i], true)) | |||||
{ | |||||
lastExplicitSettings->createNewChildElement ("MIDIINPUT") | |||||
->setAttribute ("name", midiInsFromXml[i]); | |||||
} | |||||
} | |||||
} | |||||
if (defaultMidiOutputName.isNotEmpty()) | |||||
lastExplicitSettings->setAttribute ("defaultMidiOutput", defaultMidiOutputName); | |||||
} | |||||
//============================================================================== | |||||
void AudioDeviceManager::addAudioCallback (AudioIODeviceCallback* newCallback) | |||||
{ | |||||
{ | |||||
const ScopedLock sl (audioCallbackLock); | |||||
if (callbacks.contains (newCallback)) | |||||
return; | |||||
} | |||||
if (currentAudioDevice != nullptr && newCallback != nullptr) | |||||
newCallback->audioDeviceAboutToStart (currentAudioDevice); | |||||
const ScopedLock sl (audioCallbackLock); | |||||
callbacks.add (newCallback); | |||||
} | |||||
void AudioDeviceManager::removeAudioCallback (AudioIODeviceCallback* callbackToRemove) | |||||
{ | |||||
if (callbackToRemove != nullptr) | |||||
{ | |||||
bool needsDeinitialising = currentAudioDevice != nullptr; | |||||
{ | |||||
const ScopedLock sl (audioCallbackLock); | |||||
needsDeinitialising = needsDeinitialising && callbacks.contains (callbackToRemove); | |||||
callbacks.removeFirstMatchingValue (callbackToRemove); | |||||
} | |||||
if (needsDeinitialising) | |||||
callbackToRemove->audioDeviceStopped(); | |||||
} | |||||
} | |||||
void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelData, | |||||
int numInputChannels, | |||||
float** outputChannelData, | |||||
int numOutputChannels, | |||||
int numSamples) | |||||
{ | |||||
const ScopedLock sl (audioCallbackLock); | |||||
if (inputLevelMeasurementEnabledCount > 0 && numInputChannels > 0) | |||||
{ | |||||
for (int j = 0; j < numSamples; ++j) | |||||
{ | |||||
float s = 0; | |||||
for (int i = 0; i < numInputChannels; ++i) | |||||
s += std::abs (inputChannelData[i][j]); | |||||
s /= numInputChannels; | |||||
const double decayFactor = 0.99992; | |||||
if (s > inputLevel) | |||||
inputLevel = s; | |||||
else if (inputLevel > 0.001f) | |||||
inputLevel *= decayFactor; | |||||
else | |||||
inputLevel = 0; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
inputLevel = 0; | |||||
} | |||||
if (callbacks.size() > 0) | |||||
{ | |||||
const double callbackStartTime = Time::getMillisecondCounterHiRes(); | |||||
tempBuffer.setSize (jmax (1, numOutputChannels), jmax (1, numSamples), false, false, true); | |||||
callbacks.getUnchecked(0)->audioDeviceIOCallback (inputChannelData, numInputChannels, | |||||
outputChannelData, numOutputChannels, numSamples); | |||||
float** const tempChans = tempBuffer.getArrayOfChannels(); | |||||
for (int i = callbacks.size(); --i > 0;) | |||||
{ | |||||
callbacks.getUnchecked(i)->audioDeviceIOCallback (inputChannelData, numInputChannels, | |||||
tempChans, numOutputChannels, numSamples); | |||||
for (int chan = 0; chan < numOutputChannels; ++chan) | |||||
{ | |||||
if (const float* const src = tempChans [chan]) | |||||
if (float* const dst = outputChannelData [chan]) | |||||
for (int j = 0; j < numSamples; ++j) | |||||
dst[j] += src[j]; | |||||
} | |||||
} | |||||
const double msTaken = Time::getMillisecondCounterHiRes() - callbackStartTime; | |||||
const double filterAmount = 0.2; | |||||
cpuUsageMs += filterAmount * (msTaken - cpuUsageMs); | |||||
} | |||||
else | |||||
{ | |||||
for (int i = 0; i < numOutputChannels; ++i) | |||||
zeromem (outputChannelData[i], sizeof (float) * (size_t) numSamples); | |||||
} | |||||
if (testSound != nullptr) | |||||
{ | |||||
const int numSamps = jmin (numSamples, testSound->getNumSamples() - testSoundPosition); | |||||
const float* const src = testSound->getSampleData (0, testSoundPosition); | |||||
for (int i = 0; i < numOutputChannels; ++i) | |||||
for (int j = 0; j < numSamps; ++j) | |||||
outputChannelData [i][j] += src[j]; | |||||
testSoundPosition += numSamps; | |||||
if (testSoundPosition >= testSound->getNumSamples()) | |||||
testSound = nullptr; | |||||
} | |||||
} | |||||
void AudioDeviceManager::audioDeviceAboutToStartInt (AudioIODevice* const device) | |||||
{ | |||||
cpuUsageMs = 0; | |||||
const double sampleRate = device->getCurrentSampleRate(); | |||||
const int blockSize = device->getCurrentBufferSizeSamples(); | |||||
if (sampleRate > 0.0 && blockSize > 0) | |||||
{ | |||||
const double msPerBlock = 1000.0 * blockSize / sampleRate; | |||||
timeToCpuScale = (msPerBlock > 0.0) ? (1.0 / msPerBlock) : 0.0; | |||||
} | |||||
{ | |||||
const ScopedLock sl (audioCallbackLock); | |||||
for (int i = callbacks.size(); --i >= 0;) | |||||
callbacks.getUnchecked(i)->audioDeviceAboutToStart (device); | |||||
} | |||||
sendChangeMessage(); | |||||
} | |||||
void AudioDeviceManager::audioDeviceStoppedInt() | |||||
{ | |||||
cpuUsageMs = 0; | |||||
timeToCpuScale = 0; | |||||
sendChangeMessage(); | |||||
const ScopedLock sl (audioCallbackLock); | |||||
for (int i = callbacks.size(); --i >= 0;) | |||||
callbacks.getUnchecked(i)->audioDeviceStopped(); | |||||
} | |||||
void AudioDeviceManager::audioDeviceErrorInt (const String& message) | |||||
{ | |||||
const ScopedLock sl (audioCallbackLock); | |||||
for (int i = callbacks.size(); --i >= 0;) | |||||
callbacks.getUnchecked(i)->audioDeviceError (message); | |||||
} | |||||
double AudioDeviceManager::getCpuUsage() const | |||||
{ | |||||
return jlimit (0.0, 1.0, timeToCpuScale * cpuUsageMs); | |||||
} | |||||
//============================================================================== | |||||
void AudioDeviceManager::setMidiInputEnabled (const String& name, const bool enabled) | |||||
{ | |||||
if (enabled != isMidiInputEnabled (name)) | |||||
{ | |||||
if (enabled) | |||||
{ | |||||
const int index = MidiInput::getDevices().indexOf (name); | |||||
if (index >= 0) | |||||
{ | |||||
if (MidiInput* const midiIn = MidiInput::openDevice (index, callbackHandler)) | |||||
{ | |||||
enabledMidiInputs.add (midiIn); | |||||
midiIn->start(); | |||||
} | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
for (int i = enabledMidiInputs.size(); --i >= 0;) | |||||
if (enabledMidiInputs[i]->getName() == name) | |||||
enabledMidiInputs.remove (i); | |||||
} | |||||
updateXml(); | |||||
sendChangeMessage(); | |||||
} | |||||
} | |||||
bool AudioDeviceManager::isMidiInputEnabled (const String& name) const | |||||
{ | |||||
for (int i = enabledMidiInputs.size(); --i >= 0;) | |||||
if (enabledMidiInputs[i]->getName() == name) | |||||
return true; | |||||
return false; | |||||
} | |||||
void AudioDeviceManager::addMidiInputCallback (const String& name, MidiInputCallback* callbackToAdd) | |||||
{ | |||||
removeMidiInputCallback (name, callbackToAdd); | |||||
if (name.isEmpty() || isMidiInputEnabled (name)) | |||||
{ | |||||
const ScopedLock sl (midiCallbackLock); | |||||
midiCallbacks.add (callbackToAdd); | |||||
midiCallbackDevices.add (name); | |||||
} | |||||
} | |||||
void AudioDeviceManager::removeMidiInputCallback (const String& name, MidiInputCallback* callbackToRemove) | |||||
{ | |||||
for (int i = midiCallbacks.size(); --i >= 0;) | |||||
{ | |||||
if (midiCallbackDevices[i] == name && midiCallbacks.getUnchecked(i) == callbackToRemove) | |||||
{ | |||||
const ScopedLock sl (midiCallbackLock); | |||||
midiCallbacks.remove (i); | |||||
midiCallbackDevices.remove (i); | |||||
} | |||||
} | |||||
} | |||||
void AudioDeviceManager::handleIncomingMidiMessageInt (MidiInput* source, const MidiMessage& message) | |||||
{ | |||||
if (! message.isActiveSense()) | |||||
{ | |||||
const bool isDefaultSource = (source == nullptr || source == enabledMidiInputs.getFirst()); | |||||
const ScopedLock sl (midiCallbackLock); | |||||
for (int i = midiCallbackDevices.size(); --i >= 0;) | |||||
{ | |||||
const String name (midiCallbackDevices[i]); | |||||
if ((isDefaultSource && name.isEmpty()) || (name.isNotEmpty() && name == source->getName())) | |||||
midiCallbacks.getUnchecked(i)->handleIncomingMidiMessage (source, message); | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void AudioDeviceManager::setDefaultMidiOutput (const String& deviceName) | |||||
{ | |||||
if (defaultMidiOutputName != deviceName) | |||||
{ | |||||
Array <AudioIODeviceCallback*> oldCallbacks; | |||||
{ | |||||
const ScopedLock sl (audioCallbackLock); | |||||
oldCallbacks = callbacks; | |||||
callbacks.clear(); | |||||
} | |||||
if (currentAudioDevice != nullptr) | |||||
for (int i = oldCallbacks.size(); --i >= 0;) | |||||
oldCallbacks.getUnchecked(i)->audioDeviceStopped(); | |||||
defaultMidiOutput = nullptr; | |||||
defaultMidiOutputName = deviceName; | |||||
if (deviceName.isNotEmpty()) | |||||
defaultMidiOutput = MidiOutput::openDevice (MidiOutput::getDevices().indexOf (deviceName)); | |||||
if (currentAudioDevice != nullptr) | |||||
for (int i = oldCallbacks.size(); --i >= 0;) | |||||
oldCallbacks.getUnchecked(i)->audioDeviceAboutToStart (currentAudioDevice); | |||||
{ | |||||
const ScopedLock sl (audioCallbackLock); | |||||
callbacks = oldCallbacks; | |||||
} | |||||
updateXml(); | |||||
sendChangeMessage(); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void AudioDeviceManager::playTestSound() | |||||
{ | |||||
{ // cunningly nested to swap, unlock and delete in that order. | |||||
ScopedPointer <AudioSampleBuffer> oldSound; | |||||
{ | |||||
const ScopedLock sl (audioCallbackLock); | |||||
oldSound = testSound; | |||||
} | |||||
} | |||||
testSoundPosition = 0; | |||||
if (currentAudioDevice != nullptr) | |||||
{ | |||||
const double sampleRate = currentAudioDevice->getCurrentSampleRate(); | |||||
const int soundLength = (int) sampleRate; | |||||
AudioSampleBuffer* const newSound = new AudioSampleBuffer (1, soundLength); | |||||
float* samples = newSound->getSampleData (0); | |||||
const double frequency = 440.0; | |||||
const float amplitude = 0.5f; | |||||
const double phasePerSample = double_Pi * 2.0 / (sampleRate / frequency); | |||||
for (int i = 0; i < soundLength; ++i) | |||||
samples[i] = amplitude * (float) std::sin (i * phasePerSample); | |||||
newSound->applyGainRamp (0, 0, soundLength / 10, 0.0f, 1.0f); | |||||
newSound->applyGainRamp (0, soundLength - soundLength / 4, soundLength / 4, 1.0f, 0.0f); | |||||
const ScopedLock sl (audioCallbackLock); | |||||
testSound = newSound; | |||||
} | |||||
} | |||||
void AudioDeviceManager::enableInputLevelMeasurement (const bool enableMeasurement) | |||||
{ | |||||
const ScopedLock sl (audioCallbackLock); | |||||
if (enableMeasurement) | |||||
++inputLevelMeasurementEnabledCount; | |||||
else | |||||
--inputLevelMeasurementEnabledCount; | |||||
inputLevel = 0; | |||||
} | |||||
double AudioDeviceManager::getCurrentInputLevel() const | |||||
{ | |||||
jassert (inputLevelMeasurementEnabledCount > 0); // you need to call enableInputLevelMeasurement() before using this! | |||||
return inputLevel; | |||||
} |
@@ -0,0 +1,512 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_AUDIODEVICEMANAGER_JUCEHEADER__ | |||||
#define __JUCE_AUDIODEVICEMANAGER_JUCEHEADER__ | |||||
#include "juce_AudioIODeviceType.h" | |||||
#include "../midi_io/juce_MidiInput.h" | |||||
#include "../midi_io/juce_MidiOutput.h" | |||||
//============================================================================== | |||||
/** | |||||
Manages the state of some audio and midi i/o devices. | |||||
This class keeps tracks of a currently-selected audio device, through | |||||
with which it continuously streams data from an audio callback, as well as | |||||
one or more midi inputs. | |||||
The idea is that your application will create one global instance of this object, | |||||
and let it take care of creating and deleting specific types of audio devices | |||||
internally. So when the device is changed, your callbacks will just keep running | |||||
without having to worry about this. | |||||
The manager can save and reload all of its device settings as XML, which | |||||
makes it very easy for you to save and reload the audio setup of your | |||||
application. | |||||
And to make it easy to let the user change its settings, there's a component | |||||
to do just that - the AudioDeviceSelectorComponent class, which contains a set of | |||||
device selection/sample-rate/latency controls. | |||||
To use an AudioDeviceManager, create one, and use initialise() to set it up. Then | |||||
call addAudioCallback() to register your audio callback with it, and use that to process | |||||
your audio data. | |||||
The manager also acts as a handy hub for incoming midi messages, allowing a | |||||
listener to register for messages from either a specific midi device, or from whatever | |||||
the current default midi input device is. The listener then doesn't have to worry about | |||||
re-registering with different midi devices if they are changed or deleted. | |||||
And yet another neat trick is that amount of CPU time being used is measured and | |||||
available with the getCpuUsage() method. | |||||
The AudioDeviceManager is a ChangeBroadcaster, and will send a change message to | |||||
listeners whenever one of its settings is changed. | |||||
@see AudioDeviceSelectorComponent, AudioIODevice, AudioIODeviceType | |||||
*/ | |||||
class JUCE_API AudioDeviceManager : public ChangeBroadcaster | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a default AudioDeviceManager. | |||||
Initially no audio device will be selected. You should call the initialise() method | |||||
and register an audio callback with setAudioCallback() before it'll be able to | |||||
actually make any noise. | |||||
*/ | |||||
AudioDeviceManager(); | |||||
/** Destructor. */ | |||||
~AudioDeviceManager(); | |||||
//============================================================================== | |||||
/** | |||||
This structure holds a set of properties describing the current audio setup. | |||||
An AudioDeviceManager uses this class to save/load its current settings, and to | |||||
specify your preferred options when opening a device. | |||||
@see AudioDeviceManager::setAudioDeviceSetup(), AudioDeviceManager::initialise() | |||||
*/ | |||||
struct JUCE_API AudioDeviceSetup | |||||
{ | |||||
/** Creates an AudioDeviceSetup object. | |||||
The default constructor sets all the member variables to indicate default values. | |||||
You can then fill-in any values you want to before passing the object to | |||||
AudioDeviceManager::initialise(). | |||||
*/ | |||||
AudioDeviceSetup(); | |||||
bool operator== (const AudioDeviceSetup& other) const; | |||||
/** The name of the audio device used for output. | |||||
The name has to be one of the ones listed by the AudioDeviceManager's currently | |||||
selected device type. | |||||
This may be the same as the input device. | |||||
An empty string indicates the default device. | |||||
*/ | |||||
String outputDeviceName; | |||||
/** The name of the audio device used for input. | |||||
This may be the same as the output device. | |||||
An empty string indicates the default device. | |||||
*/ | |||||
String inputDeviceName; | |||||
/** The current sample rate. | |||||
This rate is used for both the input and output devices. | |||||
A value of 0 indicates that you don't care what rate is used, and the | |||||
device will choose a sensible rate for you. | |||||
*/ | |||||
double sampleRate; | |||||
/** The buffer size, in samples. | |||||
This buffer size is used for both the input and output devices. | |||||
A value of 0 indicates the default buffer size. | |||||
*/ | |||||
int bufferSize; | |||||
/** The set of active input channels. | |||||
The bits that are set in this array indicate the channels of the | |||||
input device that are active. | |||||
If useDefaultInputChannels is true, this value is ignored. | |||||
*/ | |||||
BigInteger inputChannels; | |||||
/** If this is true, it indicates that the inputChannels array | |||||
should be ignored, and instead, the device's default channels | |||||
should be used. | |||||
*/ | |||||
bool useDefaultInputChannels; | |||||
/** The set of active output channels. | |||||
The bits that are set in this array indicate the channels of the | |||||
input device that are active. | |||||
If useDefaultOutputChannels is true, this value is ignored. | |||||
*/ | |||||
BigInteger outputChannels; | |||||
/** If this is true, it indicates that the outputChannels array | |||||
should be ignored, and instead, the device's default channels | |||||
should be used. | |||||
*/ | |||||
bool useDefaultOutputChannels; | |||||
}; | |||||
//============================================================================== | |||||
/** Opens a set of audio devices ready for use. | |||||
This will attempt to open either a default audio device, or one that was | |||||
previously saved as XML. | |||||
@param numInputChannelsNeeded a minimum number of input channels needed | |||||
by your app. | |||||
@param numOutputChannelsNeeded a minimum number of output channels to open | |||||
@param savedState either a previously-saved state that was produced | |||||
by createStateXml(), or nullptr if you want the manager | |||||
to choose the best device to open. | |||||
@param selectDefaultDeviceOnFailure if true, then if the device specified in the XML | |||||
fails to open, then a default device will be used | |||||
instead. If false, then on failure, no device is | |||||
opened. | |||||
@param preferredDefaultDeviceName if this is not empty, and there's a device with this | |||||
name, then that will be used as the default device | |||||
(assuming that there wasn't one specified in the XML). | |||||
The string can actually be a simple wildcard, containing "*" | |||||
and "?" characters | |||||
@param preferredSetupOptions if this is non-null, the structure will be used as the | |||||
set of preferred settings when opening the device. If you | |||||
use this parameter, the preferredDefaultDeviceName | |||||
field will be ignored | |||||
@returns an error message if anything went wrong, or an empty string if it worked ok. | |||||
*/ | |||||
String initialise (int numInputChannelsNeeded, | |||||
int numOutputChannelsNeeded, | |||||
const XmlElement* savedState, | |||||
bool selectDefaultDeviceOnFailure, | |||||
const String& preferredDefaultDeviceName = String::empty, | |||||
const AudioDeviceSetup* preferredSetupOptions = 0); | |||||
/** Returns some XML representing the current state of the manager. | |||||
This stores the current device, its samplerate, block size, etc, and | |||||
can be restored later with initialise(). | |||||
Note that this can return a null pointer if no settings have been explicitly changed | |||||
(i.e. if the device manager has just been left in its default state). | |||||
*/ | |||||
XmlElement* createStateXml() const; | |||||
//============================================================================== | |||||
/** Returns the current device properties that are in use. | |||||
@see setAudioDeviceSetup | |||||
*/ | |||||
void getAudioDeviceSetup (AudioDeviceSetup& setup); | |||||
/** Changes the current device or its settings. | |||||
If you want to change a device property, like the current sample rate or | |||||
block size, you can call getAudioDeviceSetup() to retrieve the current | |||||
settings, then tweak the appropriate fields in the AudioDeviceSetup structure, | |||||
and pass it back into this method to apply the new settings. | |||||
@param newSetup the settings that you'd like to use | |||||
@param treatAsChosenDevice if this is true and if the device opens correctly, these new | |||||
settings will be taken as having been explicitly chosen by the | |||||
user, and the next time createStateXml() is called, these settings | |||||
will be returned. If it's false, then the device is treated as a | |||||
temporary or default device, and a call to createStateXml() will | |||||
return either the last settings that were made with treatAsChosenDevice | |||||
as true, or the last XML settings that were passed into initialise(). | |||||
@returns an error message if anything went wrong, or an empty string if it worked ok. | |||||
@see getAudioDeviceSetup | |||||
*/ | |||||
String setAudioDeviceSetup (const AudioDeviceSetup& newSetup, | |||||
bool treatAsChosenDevice); | |||||
/** Returns the currently-active audio device. */ | |||||
AudioIODevice* getCurrentAudioDevice() const noexcept { return currentAudioDevice; } | |||||
/** Returns the type of audio device currently in use. | |||||
@see setCurrentAudioDeviceType | |||||
*/ | |||||
String getCurrentAudioDeviceType() const { return currentDeviceType; } | |||||
/** Returns the currently active audio device type object. | |||||
Don't keep a copy of this pointer - it's owned by the device manager and could | |||||
change at any time. | |||||
*/ | |||||
AudioIODeviceType* getCurrentDeviceTypeObject() const; | |||||
/** Changes the class of audio device being used. | |||||
This switches between, e.g. ASIO and DirectSound. On the Mac you probably won't ever call | |||||
this because there's only one type: CoreAudio. | |||||
For a list of types, see getAvailableDeviceTypes(). | |||||
*/ | |||||
void setCurrentAudioDeviceType (const String& type, | |||||
bool treatAsChosenDevice); | |||||
/** Closes the currently-open device. | |||||
You can call restartLastAudioDevice() later to reopen it in the same state | |||||
that it was just in. | |||||
*/ | |||||
void closeAudioDevice(); | |||||
/** Tries to reload the last audio device that was running. | |||||
Note that this only reloads the last device that was running before | |||||
closeAudioDevice() was called - it doesn't reload any kind of saved-state, | |||||
and can only be called after a device has been opened with SetAudioDevice(). | |||||
If a device is already open, this call will do nothing. | |||||
*/ | |||||
void restartLastAudioDevice(); | |||||
//============================================================================== | |||||
/** Registers an audio callback to be used. | |||||
The manager will redirect callbacks from whatever audio device is currently | |||||
in use to all registered callback objects. If more than one callback is | |||||
active, they will all be given the same input data, and their outputs will | |||||
be summed. | |||||
If necessary, this method will invoke audioDeviceAboutToStart() on the callback | |||||
object before returning. | |||||
To remove a callback, use removeAudioCallback(). | |||||
*/ | |||||
void addAudioCallback (AudioIODeviceCallback* newCallback); | |||||
/** Deregisters a previously added callback. | |||||
If necessary, this method will invoke audioDeviceStopped() on the callback | |||||
object before returning. | |||||
@see addAudioCallback | |||||
*/ | |||||
void removeAudioCallback (AudioIODeviceCallback* callback); | |||||
//============================================================================== | |||||
/** Returns the average proportion of available CPU being spent inside the audio callbacks. | |||||
Returns a value between 0 and 1.0 | |||||
*/ | |||||
double getCpuUsage() const; | |||||
//============================================================================== | |||||
/** Enables or disables a midi input device. | |||||
The list of devices can be obtained with the MidiInput::getDevices() method. | |||||
Any incoming messages from enabled input devices will be forwarded on to all the | |||||
listeners that have been registered with the addMidiInputCallback() method. They | |||||
can either register for messages from a particular device, or from just the | |||||
"default" midi input. | |||||
Routing the midi input via an AudioDeviceManager means that when a listener | |||||
registers for the default midi input, this default device can be changed by the | |||||
manager without the listeners having to know about it or re-register. | |||||
It also means that a listener can stay registered for a midi input that is disabled | |||||
or not present, so that when the input is re-enabled, the listener will start | |||||
receiving messages again. | |||||
@see addMidiInputCallback, isMidiInputEnabled | |||||
*/ | |||||
void setMidiInputEnabled (const String& midiInputDeviceName, bool enabled); | |||||
/** Returns true if a given midi input device is being used. | |||||
@see setMidiInputEnabled | |||||
*/ | |||||
bool isMidiInputEnabled (const String& midiInputDeviceName) const; | |||||
/** Registers a listener for callbacks when midi events arrive from a midi input. | |||||
The device name can be empty to indicate that it wants events from whatever the | |||||
current "default" device is. Or it can be the name of one of the midi input devices | |||||
(see MidiInput::getDevices() for the names). | |||||
Only devices which are enabled (see the setMidiInputEnabled() method) will have their | |||||
events forwarded on to listeners. | |||||
*/ | |||||
void addMidiInputCallback (const String& midiInputDeviceName, | |||||
MidiInputCallback* callback); | |||||
/** Removes a listener that was previously registered with addMidiInputCallback(). | |||||
*/ | |||||
void removeMidiInputCallback (const String& midiInputDeviceName, | |||||
MidiInputCallback* callback); | |||||
//============================================================================== | |||||
/** Sets a midi output device to use as the default. | |||||
The list of devices can be obtained with the MidiOutput::getDevices() method. | |||||
The specified device will be opened automatically and can be retrieved with the | |||||
getDefaultMidiOutput() method. | |||||
Pass in an empty string to deselect all devices. For the default device, you | |||||
can use MidiOutput::getDevices() [MidiOutput::getDefaultDeviceIndex()]. | |||||
@see getDefaultMidiOutput, getDefaultMidiOutputName | |||||
*/ | |||||
void setDefaultMidiOutput (const String& deviceName); | |||||
/** Returns the name of the default midi output. | |||||
@see setDefaultMidiOutput, getDefaultMidiOutput | |||||
*/ | |||||
String getDefaultMidiOutputName() const { return defaultMidiOutputName; } | |||||
/** Returns the current default midi output device. | |||||
If no device has been selected, or the device can't be opened, this will | |||||
return 0. | |||||
@see getDefaultMidiOutputName | |||||
*/ | |||||
MidiOutput* getDefaultMidiOutput() const noexcept { return defaultMidiOutput; } | |||||
/** Returns a list of the types of device supported. | |||||
*/ | |||||
const OwnedArray <AudioIODeviceType>& getAvailableDeviceTypes(); | |||||
//============================================================================== | |||||
/** Creates a list of available types. | |||||
This will add a set of new AudioIODeviceType objects to the specified list, to | |||||
represent each available types of device. | |||||
You can override this if your app needs to do something specific, like avoid | |||||
using DirectSound devices, etc. | |||||
*/ | |||||
virtual void createAudioDeviceTypes (OwnedArray <AudioIODeviceType>& types); | |||||
/** Adds a new device type to the list of types. | |||||
The manager will take ownership of the object that is passed-in. | |||||
*/ | |||||
void addAudioDeviceType (AudioIODeviceType* newDeviceType); | |||||
//============================================================================== | |||||
/** Plays a beep through the current audio device. | |||||
This is here to allow the audio setup UI panels to easily include a "test" | |||||
button so that the user can check where the audio is coming from. | |||||
*/ | |||||
void playTestSound(); | |||||
/** Turns on level-measuring. | |||||
When enabled, the device manager will measure the peak input level | |||||
across all channels, and you can get this level by calling getCurrentInputLevel(). | |||||
This is mainly intended for audio setup UI panels to use to create a mic | |||||
level display, so that the user can check that they've selected the right | |||||
device. | |||||
A simple filter is used to make the level decay smoothly, but this is | |||||
only intended for giving rough feedback, and not for any kind of accurate | |||||
measurement. | |||||
*/ | |||||
void enableInputLevelMeasurement (bool enableMeasurement); | |||||
/** Returns the current input level. | |||||
To use this, you must first enable it by calling enableInputLevelMeasurement(). | |||||
See enableInputLevelMeasurement() for more info. | |||||
*/ | |||||
double getCurrentInputLevel() const; | |||||
/** Returns the a lock that can be used to synchronise access to the audio callback. | |||||
Obviously while this is locked, you're blocking the audio thread from running, so | |||||
it must only be used for very brief periods when absolutely necessary. | |||||
*/ | |||||
CriticalSection& getAudioCallbackLock() noexcept { return audioCallbackLock; } | |||||
/** Returns the a lock that can be used to synchronise access to the midi callback. | |||||
Obviously while this is locked, you're blocking the midi system from running, so | |||||
it must only be used for very brief periods when absolutely necessary. | |||||
*/ | |||||
CriticalSection& getMidiCallbackLock() noexcept { return midiCallbackLock; } | |||||
private: | |||||
//============================================================================== | |||||
OwnedArray <AudioIODeviceType> availableDeviceTypes; | |||||
OwnedArray <AudioDeviceSetup> lastDeviceTypeConfigs; | |||||
AudioDeviceSetup currentSetup; | |||||
ScopedPointer <AudioIODevice> currentAudioDevice; | |||||
Array <AudioIODeviceCallback*> callbacks; | |||||
int numInputChansNeeded, numOutputChansNeeded; | |||||
String currentDeviceType; | |||||
BigInteger inputChannels, outputChannels; | |||||
ScopedPointer <XmlElement> lastExplicitSettings; | |||||
mutable bool listNeedsScanning; | |||||
bool useInputNames; | |||||
int inputLevelMeasurementEnabledCount; | |||||
double inputLevel; | |||||
ScopedPointer <AudioSampleBuffer> testSound; | |||||
int testSoundPosition; | |||||
AudioSampleBuffer tempBuffer; | |||||
StringArray midiInsFromXml; | |||||
OwnedArray <MidiInput> enabledMidiInputs; | |||||
Array <MidiInputCallback*> midiCallbacks; | |||||
StringArray midiCallbackDevices; | |||||
String defaultMidiOutputName; | |||||
ScopedPointer <MidiOutput> defaultMidiOutput; | |||||
CriticalSection audioCallbackLock, midiCallbackLock; | |||||
double cpuUsageMs, timeToCpuScale; | |||||
//============================================================================== | |||||
class CallbackHandler; | |||||
friend class CallbackHandler; | |||||
friend class ScopedPointer<CallbackHandler>; | |||||
ScopedPointer<CallbackHandler> callbackHandler; | |||||
void audioDeviceIOCallbackInt (const float** inputChannelData, int totalNumInputChannels, | |||||
float** outputChannelData, int totalNumOutputChannels, int numSamples); | |||||
void audioDeviceAboutToStartInt (AudioIODevice*); | |||||
void audioDeviceStoppedInt(); | |||||
void audioDeviceErrorInt (const String&); | |||||
void handleIncomingMidiMessageInt (MidiInput*, const MidiMessage&); | |||||
void audioDeviceListChanged(); | |||||
String restartDevice (int blockSizeToUse, double sampleRateToUse, | |||||
const BigInteger& ins, const BigInteger& outs); | |||||
void stopDevice(); | |||||
void updateXml(); | |||||
void createDeviceTypesIfNeeded(); | |||||
void scanDevicesIfNeeded(); | |||||
void deleteCurrentDevice(); | |||||
double chooseBestSampleRate (double preferred) const; | |||||
int chooseBestBufferSize (int preferred) const; | |||||
void insertDefaultDeviceNames (AudioDeviceSetup&) const; | |||||
AudioIODeviceType* findType (const String& inputName, const String& outputName); | |||||
AudioIODeviceType* findType (const String& typeName); | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioDeviceManager) | |||||
}; | |||||
#endif // __JUCE_AUDIODEVICEMANAGER_JUCEHEADER__ |
@@ -0,0 +1,49 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
AudioIODevice::AudioIODevice (const String& deviceName, const String& typeName_) | |||||
: name (deviceName), | |||||
typeName (typeName_) | |||||
{ | |||||
} | |||||
AudioIODevice::~AudioIODevice() | |||||
{ | |||||
} | |||||
bool AudioIODevice::hasControlPanel() const | |||||
{ | |||||
return false; | |||||
} | |||||
bool AudioIODevice::showControlPanel() | |||||
{ | |||||
jassertfalse; // this should only be called for devices which return true from | |||||
// their hasControlPanel() method. | |||||
return false; | |||||
} | |||||
//============================================================================== | |||||
void AudioIODeviceCallback::audioDeviceError (const String&) {} |
@@ -0,0 +1,333 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_AUDIOIODEVICE_JUCEHEADER__ | |||||
#define __JUCE_AUDIOIODEVICE_JUCEHEADER__ | |||||
class AudioIODevice; | |||||
//============================================================================== | |||||
/** | |||||
One of these is passed to an AudioIODevice object to stream the audio data | |||||
in and out. | |||||
The AudioIODevice will repeatedly call this class's audioDeviceIOCallback() | |||||
method on its own high-priority audio thread, when it needs to send or receive | |||||
the next block of data. | |||||
@see AudioIODevice, AudioDeviceManager | |||||
*/ | |||||
class JUCE_API AudioIODeviceCallback | |||||
{ | |||||
public: | |||||
/** Destructor. */ | |||||
virtual ~AudioIODeviceCallback() {} | |||||
/** Processes a block of incoming and outgoing audio data. | |||||
The subclass's implementation should use the incoming audio for whatever | |||||
purposes it needs to, and must fill all the output channels with the next | |||||
block of output data before returning. | |||||
The channel data is arranged with the same array indices as the channel name | |||||
array returned by AudioIODevice::getOutputChannelNames(), but those channels | |||||
that aren't specified in AudioIODevice::open() will have a null pointer for their | |||||
associated channel, so remember to check for this. | |||||
@param inputChannelData a set of arrays containing the audio data for each | |||||
incoming channel - this data is valid until the function | |||||
returns. There will be one channel of data for each input | |||||
channel that was enabled when the audio device was opened | |||||
(see AudioIODevice::open()) | |||||
@param numInputChannels the number of pointers to channel data in the | |||||
inputChannelData array. | |||||
@param outputChannelData a set of arrays which need to be filled with the data | |||||
that should be sent to each outgoing channel of the device. | |||||
There will be one channel of data for each output channel | |||||
that was enabled when the audio device was opened (see | |||||
AudioIODevice::open()) | |||||
The initial contents of the array is undefined, so the | |||||
callback function must fill all the channels with zeros if | |||||
its output is silence. Failing to do this could cause quite | |||||
an unpleasant noise! | |||||
@param numOutputChannels the number of pointers to channel data in the | |||||
outputChannelData array. | |||||
@param numSamples the number of samples in each channel of the input and | |||||
output arrays. The number of samples will depend on the | |||||
audio device's buffer size and will usually remain constant, | |||||
although this isn't guaranteed, so make sure your code can | |||||
cope with reasonable changes in the buffer size from one | |||||
callback to the next. | |||||
*/ | |||||
virtual void audioDeviceIOCallback (const float** inputChannelData, | |||||
int numInputChannels, | |||||
float** outputChannelData, | |||||
int numOutputChannels, | |||||
int numSamples) = 0; | |||||
/** Called to indicate that the device is about to start calling back. | |||||
This will be called just before the audio callbacks begin, either when this | |||||
callback has just been added to an audio device, or after the device has been | |||||
restarted because of a sample-rate or block-size change. | |||||
You can use this opportunity to find out the sample rate and block size | |||||
that the device is going to use by calling the AudioIODevice::getCurrentSampleRate() | |||||
and AudioIODevice::getCurrentBufferSizeSamples() on the supplied pointer. | |||||
@param device the audio IO device that will be used to drive the callback. | |||||
Note that if you're going to store this this pointer, it is | |||||
only valid until the next time that audioDeviceStopped is called. | |||||
*/ | |||||
virtual void audioDeviceAboutToStart (AudioIODevice* device) = 0; | |||||
/** Called to indicate that the device has stopped. */ | |||||
virtual void audioDeviceStopped() = 0; | |||||
/** This can be overridden to be told if the device generates an error while operating. | |||||
Be aware that this could be called by any thread! And not all devices perform | |||||
this callback. | |||||
*/ | |||||
virtual void audioDeviceError (const String& errorMessage); | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
Base class for an audio device with synchronised input and output channels. | |||||
Subclasses of this are used to implement different protocols such as DirectSound, | |||||
ASIO, CoreAudio, etc. | |||||
To create one of these, you'll need to use the AudioIODeviceType class - see the | |||||
documentation for that class for more info. | |||||
For an easier way of managing audio devices and their settings, have a look at the | |||||
AudioDeviceManager class. | |||||
@see AudioIODeviceType, AudioDeviceManager | |||||
*/ | |||||
class JUCE_API AudioIODevice | |||||
{ | |||||
public: | |||||
/** Destructor. */ | |||||
virtual ~AudioIODevice(); | |||||
//============================================================================== | |||||
/** Returns the device's name, (as set in the constructor). */ | |||||
const String& getName() const noexcept { return name; } | |||||
/** Returns the type of the device. | |||||
E.g. "CoreAudio", "ASIO", etc. - this comes from the AudioIODeviceType that created it. | |||||
*/ | |||||
const String& getTypeName() const noexcept { return typeName; } | |||||
//============================================================================== | |||||
/** Returns the names of all the available output channels on this device. | |||||
To find out which of these are currently in use, call getActiveOutputChannels(). | |||||
*/ | |||||
virtual StringArray getOutputChannelNames() = 0; | |||||
/** Returns the names of all the available input channels on this device. | |||||
To find out which of these are currently in use, call getActiveInputChannels(). | |||||
*/ | |||||
virtual StringArray getInputChannelNames() = 0; | |||||
//============================================================================== | |||||
/** Returns the number of sample-rates this device supports. | |||||
To find out which rates are available on this device, use this method to | |||||
find out how many there are, and getSampleRate() to get the rates. | |||||
@see getSampleRate | |||||
*/ | |||||
virtual int getNumSampleRates() = 0; | |||||
/** Returns one of the sample-rates this device supports. | |||||
To find out which rates are available on this device, use getNumSampleRates() to | |||||
find out how many there are, and getSampleRate() to get the individual rates. | |||||
The sample rate is set by the open() method. | |||||
(Note that for DirectSound some rates might not work, depending on combinations | |||||
of i/o channels that are being opened). | |||||
@see getNumSampleRates | |||||
*/ | |||||
virtual double getSampleRate (int index) = 0; | |||||
/** Returns the number of sizes of buffer that are available. | |||||
@see getBufferSizeSamples, getDefaultBufferSize | |||||
*/ | |||||
virtual int getNumBufferSizesAvailable() = 0; | |||||
/** Returns one of the possible buffer-sizes. | |||||
@param index the index of the buffer-size to use, from 0 to getNumBufferSizesAvailable() - 1 | |||||
@returns a number of samples | |||||
@see getNumBufferSizesAvailable, getDefaultBufferSize | |||||
*/ | |||||
virtual int getBufferSizeSamples (int index) = 0; | |||||
/** Returns the default buffer-size to use. | |||||
@returns a number of samples | |||||
@see getNumBufferSizesAvailable, getBufferSizeSamples | |||||
*/ | |||||
virtual int getDefaultBufferSize() = 0; | |||||
//============================================================================== | |||||
/** Tries to open the device ready to play. | |||||
@param inputChannels a BigInteger in which a set bit indicates that the corresponding | |||||
input channel should be enabled | |||||
@param outputChannels a BigInteger in which a set bit indicates that the corresponding | |||||
output channel should be enabled | |||||
@param sampleRate the sample rate to try to use - to find out which rates are | |||||
available, see getNumSampleRates() and getSampleRate() | |||||
@param bufferSizeSamples the size of i/o buffer to use - to find out the available buffer | |||||
sizes, see getNumBufferSizesAvailable() and getBufferSizeSamples() | |||||
@returns an error description if there's a problem, or an empty string if it succeeds in | |||||
opening the device | |||||
@see close | |||||
*/ | |||||
virtual String open (const BigInteger& inputChannels, | |||||
const BigInteger& outputChannels, | |||||
double sampleRate, | |||||
int bufferSizeSamples) = 0; | |||||
/** Closes and releases the device if it's open. */ | |||||
virtual void close() = 0; | |||||
/** Returns true if the device is still open. | |||||
A device might spontaneously close itself if something goes wrong, so this checks if | |||||
it's still open. | |||||
*/ | |||||
virtual bool isOpen() = 0; | |||||
/** Starts the device actually playing. | |||||
This must be called after the device has been opened. | |||||
@param callback the callback to use for streaming the data. | |||||
@see AudioIODeviceCallback, open | |||||
*/ | |||||
virtual void start (AudioIODeviceCallback* callback) = 0; | |||||
/** Stops the device playing. | |||||
Once a device has been started, this will stop it. Any pending calls to the | |||||
callback class will be flushed before this method returns. | |||||
*/ | |||||
virtual void stop() = 0; | |||||
/** Returns true if the device is still calling back. | |||||
The device might mysteriously stop, so this checks whether it's | |||||
still playing. | |||||
*/ | |||||
virtual bool isPlaying() = 0; | |||||
/** Returns the last error that happened if anything went wrong. */ | |||||
virtual String getLastError() = 0; | |||||
//============================================================================== | |||||
/** Returns the buffer size that the device is currently using. | |||||
If the device isn't actually open, this value doesn't really mean much. | |||||
*/ | |||||
virtual int getCurrentBufferSizeSamples() = 0; | |||||
/** Returns the sample rate that the device is currently using. | |||||
If the device isn't actually open, this value doesn't really mean much. | |||||
*/ | |||||
virtual double getCurrentSampleRate() = 0; | |||||
/** Returns the device's current physical bit-depth. | |||||
If the device isn't actually open, this value doesn't really mean much. | |||||
*/ | |||||
virtual int getCurrentBitDepth() = 0; | |||||
/** Returns a mask showing which of the available output channels are currently | |||||
enabled. | |||||
@see getOutputChannelNames | |||||
*/ | |||||
virtual BigInteger getActiveOutputChannels() const = 0; | |||||
/** Returns a mask showing which of the available input channels are currently | |||||
enabled. | |||||
@see getInputChannelNames | |||||
*/ | |||||
virtual BigInteger getActiveInputChannels() const = 0; | |||||
/** Returns the device's output latency. | |||||
This is the delay in samples between a callback getting a block of data, and | |||||
that data actually getting played. | |||||
*/ | |||||
virtual int getOutputLatencyInSamples() = 0; | |||||
/** Returns the device's input latency. | |||||
This is the delay in samples between some audio actually arriving at the soundcard, | |||||
and the callback getting passed this block of data. | |||||
*/ | |||||
virtual int getInputLatencyInSamples() = 0; | |||||
//============================================================================== | |||||
/** True if this device can show a pop-up control panel for editing its settings. | |||||
This is generally just true of ASIO devices. If true, you can call showControlPanel() | |||||
to display it. | |||||
*/ | |||||
virtual bool hasControlPanel() const; | |||||
/** Shows a device-specific control panel if there is one. | |||||
This should only be called for devices which return true from hasControlPanel(). | |||||
*/ | |||||
virtual bool showControlPanel(); | |||||
//============================================================================== | |||||
protected: | |||||
/** Creates a device, setting its name and type member variables. */ | |||||
AudioIODevice (const String& deviceName, | |||||
const String& typeName); | |||||
/** @internal */ | |||||
String name, typeName; | |||||
}; | |||||
#endif // __JUCE_AUDIOIODEVICE_JUCEHEADER__ |
@@ -0,0 +1,79 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
AudioIODeviceType::AudioIODeviceType (const String& name) | |||||
: typeName (name) | |||||
{ | |||||
} | |||||
AudioIODeviceType::~AudioIODeviceType() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
void AudioIODeviceType::addListener (Listener* l) { listeners.add (l); } | |||||
void AudioIODeviceType::removeListener (Listener* l) { listeners.remove (l); } | |||||
void AudioIODeviceType::callDeviceChangeListeners() | |||||
{ | |||||
listeners.call (&AudioIODeviceType::Listener::audioDeviceListChanged); | |||||
} | |||||
//============================================================================== | |||||
#if ! JUCE_MAC | |||||
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_CoreAudio() { return nullptr; } | |||||
#endif | |||||
#if ! JUCE_IOS | |||||
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio() { return nullptr; } | |||||
#endif | |||||
#if ! (JUCE_WINDOWS && JUCE_WASAPI) | |||||
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI() { return nullptr; } | |||||
#endif | |||||
#if ! (JUCE_WINDOWS && JUCE_DIRECTSOUND) | |||||
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_DirectSound() { return nullptr; } | |||||
#endif | |||||
#if ! (JUCE_WINDOWS && JUCE_ASIO) | |||||
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ASIO() { return nullptr; } | |||||
#endif | |||||
#if ! (JUCE_LINUX && JUCE_ALSA) | |||||
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ALSA() { return nullptr; } | |||||
#endif | |||||
#if ! (JUCE_LINUX && JUCE_JACK) | |||||
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK() { return nullptr; } | |||||
#endif | |||||
#if ! JUCE_ANDROID | |||||
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android() { return nullptr; } | |||||
#endif | |||||
#if ! (JUCE_ANDROID && JUCE_USE_ANDROID_OPENSLES) | |||||
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES() { return nullptr; } | |||||
#endif |
@@ -0,0 +1,186 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_AUDIOIODEVICETYPE_JUCEHEADER__ | |||||
#define __JUCE_AUDIOIODEVICETYPE_JUCEHEADER__ | |||||
#include "juce_AudioIODevice.h" | |||||
class AudioDeviceManager; | |||||
//============================================================================== | |||||
/** | |||||
Represents a type of audio driver, such as DirectSound, ASIO, CoreAudio, etc. | |||||
To get a list of available audio driver types, use the AudioDeviceManager::createAudioDeviceTypes() | |||||
method. Each of the objects returned can then be used to list the available | |||||
devices of that type. E.g. | |||||
@code | |||||
OwnedArray <AudioIODeviceType> types; | |||||
myAudioDeviceManager.createAudioDeviceTypes (types); | |||||
for (int i = 0; i < types.size(); ++i) | |||||
{ | |||||
String typeName (types[i]->getTypeName()); // This will be things like "DirectSound", "CoreAudio", etc. | |||||
types[i]->scanForDevices(); // This must be called before getting the list of devices | |||||
StringArray deviceNames (types[i]->getDeviceNames()); // This will now return a list of available devices of this type | |||||
for (int j = 0; j < deviceNames.size(); ++j) | |||||
{ | |||||
AudioIODevice* device = types[i]->createDevice (deviceNames [j]); | |||||
... | |||||
} | |||||
} | |||||
@endcode | |||||
For an easier way of managing audio devices and their settings, have a look at the | |||||
AudioDeviceManager class. | |||||
@see AudioIODevice, AudioDeviceManager | |||||
*/ | |||||
class JUCE_API AudioIODeviceType | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Returns the name of this type of driver that this object manages. | |||||
This will be something like "DirectSound", "ASIO", "CoreAudio", "ALSA", etc. | |||||
*/ | |||||
const String& getTypeName() const noexcept { return typeName; } | |||||
//============================================================================== | |||||
/** Refreshes the object's cached list of known devices. | |||||
This must be called at least once before calling getDeviceNames() or any of | |||||
the other device creation methods. | |||||
*/ | |||||
virtual void scanForDevices() = 0; | |||||
/** Returns the list of available devices of this type. | |||||
The scanForDevices() method must have been called to create this list. | |||||
@param wantInputNames only really used by DirectSound where devices are split up | |||||
into inputs and outputs, this indicates whether to use | |||||
the input or output name to refer to a pair of devices. | |||||
*/ | |||||
virtual StringArray getDeviceNames (bool wantInputNames = false) const = 0; | |||||
/** Returns the name of the default device. | |||||
This will be one of the names from the getDeviceNames() list. | |||||
@param forInput if true, this means that a default input device should be | |||||
returned; if false, it should return the default output | |||||
*/ | |||||
virtual int getDefaultDeviceIndex (bool forInput) const = 0; | |||||
/** Returns the index of a given device in the list of device names. | |||||
If asInput is true, it shows the index in the inputs list, otherwise it | |||||
looks for it in the outputs list. | |||||
*/ | |||||
virtual int getIndexOfDevice (AudioIODevice* device, bool asInput) const = 0; | |||||
/** Returns true if two different devices can be used for the input and output. | |||||
*/ | |||||
virtual bool hasSeparateInputsAndOutputs() const = 0; | |||||
/** Creates one of the devices of this type. | |||||
The deviceName must be one of the strings returned by getDeviceNames(), and | |||||
scanForDevices() must have been called before this method is used. | |||||
*/ | |||||
virtual AudioIODevice* createDevice (const String& outputDeviceName, | |||||
const String& inputDeviceName) = 0; | |||||
//============================================================================== | |||||
/** | |||||
A class for receiving events when audio devices are inserted or removed. | |||||
You can register a AudioIODeviceType::Listener with an~AudioIODeviceType object | |||||
using the AudioIODeviceType::addListener() method, and it will be called when | |||||
devices of that type are added or removed. | |||||
@see AudioIODeviceType::addListener, AudioIODeviceType::removeListener | |||||
*/ | |||||
class Listener | |||||
{ | |||||
public: | |||||
virtual ~Listener() {} | |||||
/** Called when the list of available audio devices changes. */ | |||||
virtual void audioDeviceListChanged() = 0; | |||||
}; | |||||
/** Adds a listener that will be called when this type of device is added or | |||||
removed from the system. | |||||
*/ | |||||
void addListener (Listener* listener); | |||||
/** Removes a listener that was previously added with addListener(). */ | |||||
void removeListener (Listener* listener); | |||||
//============================================================================== | |||||
/** Destructor. */ | |||||
virtual ~AudioIODeviceType(); | |||||
//============================================================================== | |||||
/** Creates a CoreAudio device type if it's available on this platform, or returns null. */ | |||||
static AudioIODeviceType* createAudioIODeviceType_CoreAudio(); | |||||
/** Creates an iOS device type if it's available on this platform, or returns null. */ | |||||
static AudioIODeviceType* createAudioIODeviceType_iOSAudio(); | |||||
/** Creates a WASAPI device type if it's available on this platform, or returns null. */ | |||||
static AudioIODeviceType* createAudioIODeviceType_WASAPI(); | |||||
/** Creates a DirectSound device type if it's available on this platform, or returns null. */ | |||||
static AudioIODeviceType* createAudioIODeviceType_DirectSound(); | |||||
/** Creates an ASIO device type if it's available on this platform, or returns null. */ | |||||
static AudioIODeviceType* createAudioIODeviceType_ASIO(); | |||||
/** Creates an ALSA device type if it's available on this platform, or returns null. */ | |||||
static AudioIODeviceType* createAudioIODeviceType_ALSA(); | |||||
/** Creates a JACK device type if it's available on this platform, or returns null. */ | |||||
static AudioIODeviceType* createAudioIODeviceType_JACK(); | |||||
/** Creates an Android device type if it's available on this platform, or returns null. */ | |||||
static AudioIODeviceType* createAudioIODeviceType_Android(); | |||||
/** Creates an Android OpenSLES device type if it's available on this platform, or returns null. */ | |||||
static AudioIODeviceType* createAudioIODeviceType_OpenSLES(); | |||||
protected: | |||||
explicit AudioIODeviceType (const String& typeName); | |||||
/** Synchronously calls all the registered device list change listeners. */ | |||||
void callDeviceChangeListeners(); | |||||
private: | |||||
String typeName; | |||||
ListenerList<Listener> listeners; | |||||
JUCE_DECLARE_NON_COPYABLE (AudioIODeviceType) | |||||
}; | |||||
#endif // __JUCE_AUDIOIODEVICETYPE_JUCEHEADER__ |
@@ -0,0 +1,236 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#if defined (__JUCE_AUDIO_DEVICES_JUCEHEADER__) && ! JUCE_AMALGAMATED_INCLUDE | |||||
/* When you add this cpp file to your project, you mustn't include it in a file where you've | |||||
already included any other headers - just put it inside a file on its own, possibly with your config | |||||
flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix | |||||
header files that the compiler may be using. | |||||
*/ | |||||
#error "Incorrect use of JUCE cpp file" | |||||
#endif | |||||
// Your project must contain an AppConfig.h file with your project-specific settings in it, | |||||
// and your header search path must make it accessible to the module's files. | |||||
#include "AppConfig.h" | |||||
#include "../juce_core/native/juce_BasicNativeHeaders.h" | |||||
#include "juce_audio_devices.h" | |||||
//============================================================================== | |||||
#if JUCE_MAC | |||||
#define Point CarbonDummyPointName | |||||
#define Component CarbonDummyCompName | |||||
#import <CoreAudio/AudioHardware.h> | |||||
#import <CoreMIDI/MIDIServices.h> | |||||
#import <DiscRecording/DiscRecording.h> | |||||
#undef Point | |||||
#undef Component | |||||
#elif JUCE_IOS | |||||
#import <AudioToolbox/AudioToolbox.h> | |||||
#import <AVFoundation/AVFoundation.h> | |||||
#import <CoreMIDI/MIDIServices.h> | |||||
//============================================================================== | |||||
#elif JUCE_WINDOWS | |||||
#if JUCE_WASAPI | |||||
#pragma warning (push) | |||||
#pragma warning (disable: 4201) | |||||
#include <MMReg.h> | |||||
#include <Audioclient.h> | |||||
#include <Audiopolicy.h> | |||||
#include <Avrt.h> | |||||
#include <functiondiscoverykeys.h> | |||||
#pragma warning (pop) | |||||
#endif | |||||
#if JUCE_ASIO | |||||
/* This is very frustrating - we only need to use a handful of definitions from | |||||
a couple of the header files in Steinberg's ASIO SDK, and it'd be easy to copy | |||||
about 30 lines of code into this cpp file to create a fully stand-alone ASIO | |||||
implementation... | |||||
..unfortunately that would break Steinberg's license agreement for use of | |||||
their SDK, so I'm not allowed to do this. | |||||
This means that anyone who wants to use JUCE's ASIO abilities will have to: | |||||
1) Agree to Steinberg's licensing terms and download the ASIO SDK | |||||
(see www.steinberg.net/Steinberg/Developers.asp). | |||||
2) Enable this code with a global definition #define JUCE_ASIO 1. | |||||
3) Make sure that your header search path contains the iasiodrv.h file that | |||||
comes with the SDK. (Only about a handful of the SDK header files are actually | |||||
needed - so to simplify things, you could just copy these into your JUCE directory). | |||||
*/ | |||||
#include <iasiodrv.h> | |||||
#endif | |||||
#if JUCE_USE_CDBURNER | |||||
/* You'll need the Platform SDK for these headers - if you don't have it and don't | |||||
need to use CD-burning, then you might just want to set the JUCE_USE_CDBURNER flag | |||||
to 0, to avoid these includes. | |||||
*/ | |||||
#include <imapi.h> | |||||
#include <imapierror.h> | |||||
#endif | |||||
//============================================================================== | |||||
#elif JUCE_LINUX | |||||
#if JUCE_ALSA | |||||
/* Got an include error here? If so, you've either not got ALSA installed, or you've | |||||
not got your paths set up correctly to find its header files. | |||||
The package you need to install to get ASLA support is "libasound2-dev". | |||||
If you don't have the ALSA library and don't want to build Juce with audio support, | |||||
just set the JUCE_ALSA flag to 0. | |||||
*/ | |||||
#include <alsa/asoundlib.h> | |||||
#endif | |||||
#if JUCE_JACK | |||||
/* Got an include error here? If so, you've either not got jack-audio-connection-kit | |||||
installed, or you've not got your paths set up correctly to find its header files. | |||||
The package you need to install to get JACK support is "libjack-dev". | |||||
If you don't have the jack-audio-connection-kit library and don't want to build | |||||
Juce with low latency audio support, just set the JUCE_JACK flag to 0. | |||||
*/ | |||||
#include <jack/jack.h> | |||||
#endif | |||||
#undef SIZEOF | |||||
//============================================================================== | |||||
#elif JUCE_ANDROID | |||||
#if JUCE_USE_ANDROID_OPENSLES | |||||
#include <SLES/OpenSLES.h> | |||||
#include <SLES/OpenSLES_Android.h> | |||||
#endif | |||||
#endif | |||||
namespace juce | |||||
{ | |||||
// START_AUTOINCLUDE audio_io/*.cpp, midi_io/*.cpp, audio_cd/*.cpp, sources/*.cpp | |||||
#include "audio_io/juce_AudioDeviceManager.cpp" | |||||
#include "audio_io/juce_AudioIODevice.cpp" | |||||
#include "audio_io/juce_AudioIODeviceType.cpp" | |||||
#include "midi_io/juce_MidiMessageCollector.cpp" | |||||
#include "midi_io/juce_MidiOutput.cpp" | |||||
#include "audio_cd/juce_AudioCDReader.cpp" | |||||
#include "sources/juce_AudioSourcePlayer.cpp" | |||||
#include "sources/juce_AudioTransportSource.cpp" | |||||
// END_AUTOINCLUDE | |||||
} | |||||
//============================================================================== | |||||
using namespace juce; | |||||
namespace juce | |||||
{ | |||||
#include "native/juce_MidiDataConcatenator.h" | |||||
//============================================================================== | |||||
#if JUCE_MAC | |||||
#include "../juce_core/native/juce_osx_ObjCHelpers.h" | |||||
#include "native/juce_mac_CoreAudio.cpp" | |||||
#include "native/juce_mac_CoreMidi.cpp" | |||||
#if JUCE_USE_CDREADER | |||||
#include "native/juce_mac_AudioCDReader.mm" | |||||
#endif | |||||
#if JUCE_USE_CDBURNER | |||||
#include "native/juce_mac_AudioCDBurner.mm" | |||||
#endif | |||||
//============================================================================== | |||||
#elif JUCE_IOS | |||||
#include "native/juce_ios_Audio.cpp" | |||||
#include "native/juce_mac_CoreMidi.cpp" | |||||
//============================================================================== | |||||
#elif JUCE_WINDOWS | |||||
#include "../juce_core/native/juce_win32_ComSmartPtr.h" | |||||
#include "../juce_events/native/juce_win32_HiddenMessageWindow.h" | |||||
#if JUCE_WASAPI | |||||
#include "native/juce_win32_WASAPI.cpp" | |||||
#endif | |||||
#if JUCE_DIRECTSOUND | |||||
#include "native/juce_win32_DirectSound.cpp" | |||||
#endif | |||||
#include "native/juce_win32_Midi.cpp" | |||||
#if JUCE_ASIO | |||||
#include "native/juce_win32_ASIO.cpp" | |||||
#endif | |||||
#if JUCE_USE_CDREADER | |||||
#include "native/juce_win32_AudioCDReader.cpp" | |||||
#endif | |||||
#if JUCE_USE_CDBURNER | |||||
#include "native/juce_win32_AudioCDBurner.cpp" | |||||
#endif | |||||
//============================================================================== | |||||
#elif JUCE_LINUX | |||||
#if JUCE_ALSA | |||||
#include "native/juce_linux_ALSA.cpp" | |||||
#endif | |||||
#include "native/juce_linux_Midi.cpp" | |||||
#if JUCE_JACK | |||||
#include "native/juce_linux_JackAudio.cpp" | |||||
#endif | |||||
#if JUCE_USE_CDREADER | |||||
#include "native/juce_linux_AudioCDReader.cpp" | |||||
#endif | |||||
//============================================================================== | |||||
#elif JUCE_ANDROID | |||||
#include "../juce_core/native/juce_android_JNIHelpers.h" | |||||
#include "native/juce_android_Audio.cpp" | |||||
#include "native/juce_android_Midi.cpp" | |||||
#if JUCE_USE_ANDROID_OPENSLES | |||||
#include "native/juce_android_OpenSL.cpp" | |||||
#endif | |||||
#endif | |||||
} |
@@ -0,0 +1,139 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_AUDIO_DEVICES_JUCEHEADER__ | |||||
#define __JUCE_AUDIO_DEVICES_JUCEHEADER__ | |||||
#include "../juce_events/juce_events.h" | |||||
#include "../juce_audio_basics/juce_audio_basics.h" | |||||
#include "../juce_audio_formats/juce_audio_formats.h" | |||||
//============================================================================= | |||||
/** Config: JUCE_ASIO | |||||
Enables ASIO audio devices (MS Windows only). | |||||
Turning this on means that you'll need to have the Steinberg ASIO SDK installed | |||||
on your Windows build machine. | |||||
See the comments in the ASIOAudioIODevice class's header file for more | |||||
info about this. | |||||
*/ | |||||
#ifndef JUCE_ASIO | |||||
#define JUCE_ASIO 0 | |||||
#endif | |||||
/** Config: JUCE_WASAPI | |||||
Enables WASAPI audio devices (Windows Vista and above). | |||||
*/ | |||||
#ifndef JUCE_WASAPI | |||||
#define JUCE_WASAPI 1 | |||||
#endif | |||||
/** Config: JUCE_DIRECTSOUND | |||||
Enables DirectSound audio (MS Windows only). | |||||
*/ | |||||
#ifndef JUCE_DIRECTSOUND | |||||
#define JUCE_DIRECTSOUND 1 | |||||
#endif | |||||
/** Config: JUCE_ALSA | |||||
Enables ALSA audio devices (Linux only). | |||||
*/ | |||||
#ifndef JUCE_ALSA | |||||
#define JUCE_ALSA 1 | |||||
#endif | |||||
/** Config: JUCE_JACK | |||||
Enables JACK audio devices (Linux only). | |||||
*/ | |||||
#ifndef JUCE_JACK | |||||
#define JUCE_JACK 0 | |||||
#endif | |||||
/** Config: JUCE_USE_ANDROID_OPENSLES | |||||
Enables OpenSLES devices (Android only). | |||||
*/ | |||||
#ifndef JUCE_USE_ANDROID_OPENSLES | |||||
#if JUCE_ANDROID_API_VERSION > 8 | |||||
#define JUCE_USE_ANDROID_OPENSLES 1 | |||||
#else | |||||
#define JUCE_USE_ANDROID_OPENSLES 0 | |||||
#endif | |||||
#endif | |||||
//============================================================================= | |||||
/** Config: JUCE_USE_CDREADER | |||||
Enables the AudioCDReader class (on supported platforms). | |||||
*/ | |||||
#ifndef JUCE_USE_CDREADER | |||||
#define JUCE_USE_CDREADER 0 | |||||
#endif | |||||
/** Config: JUCE_USE_CDBURNER | |||||
Enables the AudioCDBurner class (on supported platforms). | |||||
*/ | |||||
#ifndef JUCE_USE_CDBURNER | |||||
#define JUCE_USE_CDBURNER 0 | |||||
#endif | |||||
//============================================================================= | |||||
namespace juce | |||||
{ | |||||
// START_AUTOINCLUDE audio_io, midi_io, sources, audio_cd | |||||
#ifndef __JUCE_AUDIODEVICEMANAGER_JUCEHEADER__ | |||||
#include "audio_io/juce_AudioDeviceManager.h" | |||||
#endif | |||||
#ifndef __JUCE_AUDIOIODEVICE_JUCEHEADER__ | |||||
#include "audio_io/juce_AudioIODevice.h" | |||||
#endif | |||||
#ifndef __JUCE_AUDIOIODEVICETYPE_JUCEHEADER__ | |||||
#include "audio_io/juce_AudioIODeviceType.h" | |||||
#endif | |||||
#ifndef __JUCE_MIDIINPUT_JUCEHEADER__ | |||||
#include "midi_io/juce_MidiInput.h" | |||||
#endif | |||||
#ifndef __JUCE_MIDIMESSAGECOLLECTOR_JUCEHEADER__ | |||||
#include "midi_io/juce_MidiMessageCollector.h" | |||||
#endif | |||||
#ifndef __JUCE_MIDIOUTPUT_JUCEHEADER__ | |||||
#include "midi_io/juce_MidiOutput.h" | |||||
#endif | |||||
#ifndef __JUCE_AUDIOSOURCEPLAYER_JUCEHEADER__ | |||||
#include "sources/juce_AudioSourcePlayer.h" | |||||
#endif | |||||
#ifndef __JUCE_AUDIOTRANSPORTSOURCE_JUCEHEADER__ | |||||
#include "sources/juce_AudioTransportSource.h" | |||||
#endif | |||||
#ifndef __JUCE_AUDIOCDBURNER_JUCEHEADER__ | |||||
#include "audio_cd/juce_AudioCDBurner.h" | |||||
#endif | |||||
#ifndef __JUCE_AUDIOCDREADER_JUCEHEADER__ | |||||
#include "audio_cd/juce_AudioCDReader.h" | |||||
#endif | |||||
// END_AUTOINCLUDE | |||||
} | |||||
#endif // __JUCE_AUDIO_DEVICES_JUCEHEADER__ |
@@ -0,0 +1,26 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#include "juce_audio_devices.cpp" |
@@ -0,0 +1,27 @@ | |||||
{ | |||||
"id": "juce_audio_devices", | |||||
"name": "JUCE audio and midi I/O device classes", | |||||
"version": "2.0.32", | |||||
"description": "Classes to play and record from audio and midi i/o devices.", | |||||
"website": "http://www.juce.com/juce", | |||||
"license": "GPL/Commercial", | |||||
"dependencies": [ { "id": "juce_audio_basics", "version": "matching" }, | |||||
{ "id": "juce_audio_formats", "version": "matching" }, | |||||
{ "id": "juce_events", "version": "matching" } ], | |||||
"include": "juce_audio_devices.h", | |||||
"compile": [ { "file": "juce_audio_devices.cpp", "target": "! xcode" }, | |||||
{ "file": "juce_audio_devices.mm", "target": "xcode" } ], | |||||
"browse": [ "audio_io/*", | |||||
"midi_io/*", | |||||
"sources/*", | |||||
"audio_cd/*", | |||||
"native/*" ], | |||||
"OSXFrameworks": "CoreAudio CoreMIDI DiscRecording", | |||||
"iOSFrameworks": "AudioToolbox CoreMIDI", | |||||
"LinuxLibs": "asound" | |||||
} |
@@ -0,0 +1,183 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_MIDIINPUT_JUCEHEADER__ | |||||
#define __JUCE_MIDIINPUT_JUCEHEADER__ | |||||
class MidiInput; | |||||
//============================================================================== | |||||
/** | |||||
Receives incoming messages from a physical MIDI input device. | |||||
This class is overridden to handle incoming midi messages. See the MidiInput | |||||
class for more details. | |||||
@see MidiInput | |||||
*/ | |||||
class JUCE_API MidiInputCallback | |||||
{ | |||||
public: | |||||
/** Destructor. */ | |||||
virtual ~MidiInputCallback() {} | |||||
/** Receives an incoming message. | |||||
A MidiInput object will call this method when a midi event arrives. It'll be | |||||
called on a high-priority system thread, so avoid doing anything time-consuming | |||||
in here, and avoid making any UI calls. You might find the MidiBuffer class helpful | |||||
for queueing incoming messages for use later. | |||||
@param source the MidiInput object that generated the message | |||||
@param message the incoming message. The message's timestamp is set to a value | |||||
equivalent to (Time::getMillisecondCounter() / 1000.0) to specify the | |||||
time when the message arrived. | |||||
*/ | |||||
virtual void handleIncomingMidiMessage (MidiInput* source, | |||||
const MidiMessage& message) = 0; | |||||
/** Notification sent each time a packet of a multi-packet sysex message arrives. | |||||
If a long sysex message is broken up into multiple packets, this callback is made | |||||
for each packet that arrives until the message is finished, at which point | |||||
the normal handleIncomingMidiMessage() callback will be made with the entire | |||||
message. | |||||
The message passed in will contain the start of a sysex, but won't be finished | |||||
with the terminating 0xf7 byte. | |||||
*/ | |||||
virtual void handlePartialSysexMessage (MidiInput* source, | |||||
const uint8* messageData, | |||||
int numBytesSoFar, | |||||
double timestamp) | |||||
{ | |||||
// (this bit is just to avoid compiler warnings about unused variables) | |||||
(void) source; (void) messageData; (void) numBytesSoFar; (void) timestamp; | |||||
} | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
Represents a midi input device. | |||||
To create one of these, use the static getDevices() method to find out what inputs are | |||||
available, and then use the openDevice() method to try to open one. | |||||
@see MidiOutput | |||||
*/ | |||||
class JUCE_API MidiInput | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Returns a list of the available midi input devices. | |||||
You can open one of the devices by passing its index into the | |||||
openDevice() method. | |||||
@see getDefaultDeviceIndex, openDevice | |||||
*/ | |||||
static StringArray getDevices(); | |||||
/** Returns the index of the default midi input device to use. | |||||
This refers to the index in the list returned by getDevices(). | |||||
*/ | |||||
static int getDefaultDeviceIndex(); | |||||
/** Tries to open one of the midi input devices. | |||||
This will return a MidiInput object if it manages to open it. You can then | |||||
call start() and stop() on this device, and delete it when no longer needed. | |||||
If the device can't be opened, this will return a null pointer. | |||||
@param deviceIndex the index of a device from the list returned by getDevices() | |||||
@param callback the object that will receive the midi messages from this device. | |||||
@see MidiInputCallback, getDevices | |||||
*/ | |||||
static MidiInput* openDevice (int deviceIndex, | |||||
MidiInputCallback* callback); | |||||
#if JUCE_LINUX || JUCE_MAC || JUCE_IOS || DOXYGEN | |||||
/** This will try to create a new midi input device (Not available on Windows). | |||||
This will attempt to create a new midi input device with the specified name, | |||||
for other apps to connect to. | |||||
Returns nullptr if a device can't be created. | |||||
@param deviceName the name to use for the new device | |||||
@param callback the object that will receive the midi messages from this device. | |||||
*/ | |||||
static MidiInput* createNewDevice (const String& deviceName, | |||||
MidiInputCallback* callback); | |||||
#endif | |||||
//============================================================================== | |||||
/** Destructor. */ | |||||
virtual ~MidiInput(); | |||||
/** Returns the name of this device. */ | |||||
const String& getName() const noexcept { return name; } | |||||
/** Allows you to set a custom name for the device, in case you don't like the name | |||||
it was given when created. | |||||
*/ | |||||
void setName (const String& newName) noexcept { name = newName; } | |||||
//============================================================================== | |||||
/** Starts the device running. | |||||
After calling this, the device will start sending midi messages to the | |||||
MidiInputCallback object that was specified when the openDevice() method | |||||
was called. | |||||
@see stop | |||||
*/ | |||||
virtual void start(); | |||||
/** Stops the device running. | |||||
@see start | |||||
*/ | |||||
virtual void stop(); | |||||
protected: | |||||
//============================================================================== | |||||
String name; | |||||
void* internal; | |||||
explicit MidiInput (const String& name); | |||||
private: | |||||
//============================================================================== | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInput) | |||||
}; | |||||
#endif // __JUCE_MIDIINPUT_JUCEHEADER__ |
@@ -0,0 +1,153 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
MidiMessageCollector::MidiMessageCollector() | |||||
: lastCallbackTime (0), | |||||
sampleRate (44100.0001) | |||||
{ | |||||
} | |||||
MidiMessageCollector::~MidiMessageCollector() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
void MidiMessageCollector::reset (const double sampleRate_) | |||||
{ | |||||
jassert (sampleRate_ > 0); | |||||
const ScopedLock sl (midiCallbackLock); | |||||
sampleRate = sampleRate_; | |||||
incomingMessages.clear(); | |||||
lastCallbackTime = Time::getMillisecondCounterHiRes(); | |||||
} | |||||
void MidiMessageCollector::addMessageToQueue (const MidiMessage& message) | |||||
{ | |||||
// you need to call reset() to set the correct sample rate before using this object | |||||
jassert (sampleRate != 44100.0001); | |||||
// the messages that come in here need to be time-stamped correctly - see MidiInput | |||||
// for details of what the number should be. | |||||
jassert (message.getTimeStamp() != 0); | |||||
const ScopedLock sl (midiCallbackLock); | |||||
const int sampleNumber | |||||
= (int) ((message.getTimeStamp() - 0.001 * lastCallbackTime) * sampleRate); | |||||
incomingMessages.addEvent (message, sampleNumber); | |||||
// if the messages don't get used for over a second, we'd better | |||||
// get rid of any old ones to avoid the queue getting too big | |||||
if (sampleNumber > sampleRate) | |||||
incomingMessages.clear (0, sampleNumber - (int) sampleRate); | |||||
} | |||||
void MidiMessageCollector::removeNextBlockOfMessages (MidiBuffer& destBuffer, | |||||
const int numSamples) | |||||
{ | |||||
// you need to call reset() to set the correct sample rate before using this object | |||||
jassert (sampleRate != 44100.0001); | |||||
const double timeNow = Time::getMillisecondCounterHiRes(); | |||||
const double msElapsed = timeNow - lastCallbackTime; | |||||
const ScopedLock sl (midiCallbackLock); | |||||
lastCallbackTime = timeNow; | |||||
if (! incomingMessages.isEmpty()) | |||||
{ | |||||
int numSourceSamples = jmax (1, roundToInt (msElapsed * 0.001 * sampleRate)); | |||||
int startSample = 0; | |||||
int scale = 1 << 16; | |||||
const uint8* midiData; | |||||
int numBytes, samplePosition; | |||||
MidiBuffer::Iterator iter (incomingMessages); | |||||
if (numSourceSamples > numSamples) | |||||
{ | |||||
// if our list of events is longer than the buffer we're being | |||||
// asked for, scale them down to squeeze them all in.. | |||||
const int maxBlockLengthToUse = numSamples << 5; | |||||
if (numSourceSamples > maxBlockLengthToUse) | |||||
{ | |||||
startSample = numSourceSamples - maxBlockLengthToUse; | |||||
numSourceSamples = maxBlockLengthToUse; | |||||
iter.setNextSamplePosition (startSample); | |||||
} | |||||
scale = (numSamples << 10) / numSourceSamples; | |||||
while (iter.getNextEvent (midiData, numBytes, samplePosition)) | |||||
{ | |||||
samplePosition = ((samplePosition - startSample) * scale) >> 10; | |||||
destBuffer.addEvent (midiData, numBytes, | |||||
jlimit (0, numSamples - 1, samplePosition)); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
// if our event list is shorter than the number we need, put them | |||||
// towards the end of the buffer | |||||
startSample = numSamples - numSourceSamples; | |||||
while (iter.getNextEvent (midiData, numBytes, samplePosition)) | |||||
{ | |||||
destBuffer.addEvent (midiData, numBytes, | |||||
jlimit (0, numSamples - 1, samplePosition + startSample)); | |||||
} | |||||
} | |||||
incomingMessages.clear(); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void MidiMessageCollector::handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) | |||||
{ | |||||
MidiMessage m (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity)); | |||||
m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001); | |||||
addMessageToQueue (m); | |||||
} | |||||
void MidiMessageCollector::handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber) | |||||
{ | |||||
MidiMessage m (MidiMessage::noteOff (midiChannel, midiNoteNumber)); | |||||
m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001); | |||||
addMessageToQueue (m); | |||||
} | |||||
void MidiMessageCollector::handleIncomingMidiMessage (MidiInput*, const MidiMessage& message) | |||||
{ | |||||
addMessageToQueue (message); | |||||
} |
@@ -0,0 +1,105 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_MIDIMESSAGECOLLECTOR_JUCEHEADER__ | |||||
#define __JUCE_MIDIMESSAGECOLLECTOR_JUCEHEADER__ | |||||
#include "juce_MidiInput.h" | |||||
//============================================================================== | |||||
/** | |||||
Collects incoming realtime MIDI messages and turns them into blocks suitable for | |||||
processing by a block-based audio callback. | |||||
The class can also be used as either a MidiKeyboardStateListener or a MidiInputCallback | |||||
so it can easily use a midi input or keyboard component as its source. | |||||
@see MidiMessage, MidiInput | |||||
*/ | |||||
class JUCE_API MidiMessageCollector : public MidiKeyboardStateListener, | |||||
public MidiInputCallback | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a MidiMessageCollector. */ | |||||
MidiMessageCollector(); | |||||
/** Destructor. */ | |||||
~MidiMessageCollector(); | |||||
//============================================================================== | |||||
/** Clears any messages from the queue. | |||||
You need to call this method before starting to use the collector, so that | |||||
it knows the correct sample rate to use. | |||||
*/ | |||||
void reset (double sampleRate); | |||||
/** Takes an incoming real-time message and adds it to the queue. | |||||
The message's timestamp is taken, and it will be ready for retrieval as part | |||||
of the block returned by the next call to removeNextBlockOfMessages(). | |||||
This method is fully thread-safe when overlapping calls are made with | |||||
removeNextBlockOfMessages(). | |||||
*/ | |||||
void addMessageToQueue (const MidiMessage& message); | |||||
/** Removes all the pending messages from the queue as a buffer. | |||||
This will also correct the messages' timestamps to make sure they're in | |||||
the range 0 to numSamples - 1. | |||||
This call should be made regularly by something like an audio processing | |||||
callback, because the time that it happens is used in calculating the | |||||
midi event positions. | |||||
This method is fully thread-safe when overlapping calls are made with | |||||
addMessageToQueue(). | |||||
*/ | |||||
void removeNextBlockOfMessages (MidiBuffer& destBuffer, int numSamples); | |||||
//============================================================================== | |||||
/** @internal */ | |||||
void handleNoteOn (MidiKeyboardState* source, int midiChannel, int midiNoteNumber, float velocity); | |||||
/** @internal */ | |||||
void handleNoteOff (MidiKeyboardState* source, int midiChannel, int midiNoteNumber); | |||||
/** @internal */ | |||||
void handleIncomingMidiMessage (MidiInput* source, const MidiMessage& message); | |||||
private: | |||||
//============================================================================== | |||||
double lastCallbackTime; | |||||
CriticalSection midiCallbackLock; | |||||
MidiBuffer incomingMessages; | |||||
double sampleRate; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiMessageCollector) | |||||
}; | |||||
#endif // __JUCE_MIDIMESSAGECOLLECTOR_JUCEHEADER__ |
@@ -0,0 +1,163 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
struct MidiOutput::PendingMessage | |||||
{ | |||||
PendingMessage (const void* const data, const int len, const double timeStamp) | |||||
: message (data, len, timeStamp) | |||||
{} | |||||
MidiMessage message; | |||||
PendingMessage* next; | |||||
}; | |||||
MidiOutput::MidiOutput() | |||||
: Thread ("midi out"), | |||||
internal (nullptr), | |||||
firstMessage (nullptr) | |||||
{ | |||||
} | |||||
void MidiOutput::sendBlockOfMessages (const MidiBuffer& buffer, | |||||
const double millisecondCounterToStartAt, | |||||
double samplesPerSecondForBuffer) | |||||
{ | |||||
// You've got to call startBackgroundThread() for this to actually work.. | |||||
jassert (isThreadRunning()); | |||||
// this needs to be a value in the future - RTFM for this method! | |||||
jassert (millisecondCounterToStartAt > 0); | |||||
const double timeScaleFactor = 1000.0 / samplesPerSecondForBuffer; | |||||
MidiBuffer::Iterator i (buffer); | |||||
const uint8* data; | |||||
int len, time; | |||||
while (i.getNextEvent (data, len, time)) | |||||
{ | |||||
const double eventTime = millisecondCounterToStartAt + timeScaleFactor * time; | |||||
PendingMessage* const m = new PendingMessage (data, len, eventTime); | |||||
const ScopedLock sl (lock); | |||||
if (firstMessage == nullptr || firstMessage->message.getTimeStamp() > eventTime) | |||||
{ | |||||
m->next = firstMessage; | |||||
firstMessage = m; | |||||
} | |||||
else | |||||
{ | |||||
PendingMessage* mm = firstMessage; | |||||
while (mm->next != nullptr && mm->next->message.getTimeStamp() <= eventTime) | |||||
mm = mm->next; | |||||
m->next = mm->next; | |||||
mm->next = m; | |||||
} | |||||
} | |||||
notify(); | |||||
} | |||||
void MidiOutput::clearAllPendingMessages() | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
while (firstMessage != nullptr) | |||||
{ | |||||
PendingMessage* const m = firstMessage; | |||||
firstMessage = firstMessage->next; | |||||
delete m; | |||||
} | |||||
} | |||||
void MidiOutput::startBackgroundThread() | |||||
{ | |||||
startThread (9); | |||||
} | |||||
void MidiOutput::stopBackgroundThread() | |||||
{ | |||||
stopThread (5000); | |||||
} | |||||
void MidiOutput::run() | |||||
{ | |||||
while (! threadShouldExit()) | |||||
{ | |||||
uint32 now = Time::getMillisecondCounter(); | |||||
uint32 eventTime = 0; | |||||
uint32 timeToWait = 500; | |||||
PendingMessage* message; | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
message = firstMessage; | |||||
if (message != nullptr) | |||||
{ | |||||
eventTime = (uint32) roundToInt (message->message.getTimeStamp()); | |||||
if (eventTime > now + 20) | |||||
{ | |||||
timeToWait = eventTime - (now + 20); | |||||
message = nullptr; | |||||
} | |||||
else | |||||
{ | |||||
firstMessage = message->next; | |||||
} | |||||
} | |||||
} | |||||
if (message != nullptr) | |||||
{ | |||||
const ScopedPointer<PendingMessage> messageDeleter (message); | |||||
if (eventTime > now) | |||||
{ | |||||
Time::waitForMillisecondCounter (eventTime); | |||||
if (threadShouldExit()) | |||||
break; | |||||
} | |||||
if (eventTime > now - 200) | |||||
sendMessageNow (message->message); | |||||
} | |||||
else | |||||
{ | |||||
jassert (timeToWait < 1000 * 30); | |||||
wait ((int) timeToWait); | |||||
} | |||||
} | |||||
clearAllPendingMessages(); | |||||
} |
@@ -0,0 +1,148 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_MIDIOUTPUT_JUCEHEADER__ | |||||
#define __JUCE_MIDIOUTPUT_JUCEHEADER__ | |||||
//============================================================================== | |||||
/** | |||||
Controls a physical MIDI output device. | |||||
To create one of these, use the static getDevices() method to get a list of the | |||||
available output devices, then use the openDevice() method to try to open one. | |||||
@see MidiInput | |||||
*/ | |||||
class JUCE_API MidiOutput : private Thread | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Returns a list of the available midi output devices. | |||||
You can open one of the devices by passing its index into the | |||||
openDevice() method. | |||||
@see getDefaultDeviceIndex, openDevice | |||||
*/ | |||||
static StringArray getDevices(); | |||||
/** Returns the index of the default midi output device to use. | |||||
This refers to the index in the list returned by getDevices(). | |||||
*/ | |||||
static int getDefaultDeviceIndex(); | |||||
/** Tries to open one of the midi output devices. | |||||
This will return a MidiOutput object if it manages to open it. You can then | |||||
send messages to this device, and delete it when no longer needed. | |||||
If the device can't be opened, this will return a null pointer. | |||||
@param deviceIndex the index of a device from the list returned by getDevices() | |||||
@see getDevices | |||||
*/ | |||||
static MidiOutput* openDevice (int deviceIndex); | |||||
#if JUCE_LINUX || JUCE_MAC || JUCE_IOS || DOXYGEN | |||||
/** This will try to create a new midi output device (Not available on Windows). | |||||
This will attempt to create a new midi output device that other apps can connect | |||||
to and use as their midi input. | |||||
Returns nullptr if a device can't be created. | |||||
@param deviceName the name to use for the new device | |||||
*/ | |||||
static MidiOutput* createNewDevice (const String& deviceName); | |||||
#endif | |||||
//============================================================================== | |||||
/** Destructor. */ | |||||
virtual ~MidiOutput(); | |||||
/** Makes this device output a midi message. | |||||
@see MidiMessage | |||||
*/ | |||||
virtual void sendMessageNow (const MidiMessage& message); | |||||
//============================================================================== | |||||
/** This lets you supply a block of messages that will be sent out at some point | |||||
in the future. | |||||
The MidiOutput class has an internal thread that can send out timestamped | |||||
messages - this appends a set of messages to its internal buffer, ready for | |||||
sending. | |||||
This will only work if you've already started the thread with startBackgroundThread(). | |||||
A time is supplied, at which the block of messages should be sent. This time uses | |||||
the same time base as Time::getMillisecondCounter(), and must be in the future. | |||||
The samplesPerSecondForBuffer parameter indicates the number of samples per second | |||||
used by the MidiBuffer. Each event in a MidiBuffer has a sample position, and the | |||||
samplesPerSecondForBuffer value is needed to convert this sample position to a | |||||
real time. | |||||
*/ | |||||
virtual void sendBlockOfMessages (const MidiBuffer& buffer, | |||||
double millisecondCounterToStartAt, | |||||
double samplesPerSecondForBuffer); | |||||
/** Gets rid of any midi messages that had been added by sendBlockOfMessages(). | |||||
*/ | |||||
virtual void clearAllPendingMessages(); | |||||
/** Starts up a background thread so that the device can send blocks of data. | |||||
Call this to get the device ready, before using sendBlockOfMessages(). | |||||
*/ | |||||
virtual void startBackgroundThread(); | |||||
/** Stops the background thread, and clears any pending midi events. | |||||
@see startBackgroundThread | |||||
*/ | |||||
virtual void stopBackgroundThread(); | |||||
protected: | |||||
//============================================================================== | |||||
void* internal; | |||||
CriticalSection lock; | |||||
struct PendingMessage; | |||||
PendingMessage* firstMessage; | |||||
MidiOutput(); | |||||
void run(); | |||||
private: | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutput) | |||||
}; | |||||
#endif // __JUCE_MIDIOUTPUT_JUCEHEADER__ |
@@ -0,0 +1,176 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_MIDIDATACONCATENATOR_JUCEHEADER__ | |||||
#define __JUCE_MIDIDATACONCATENATOR_JUCEHEADER__ | |||||
//============================================================================== | |||||
/** | |||||
Helper class that takes chunks of incoming midi bytes, packages them into | |||||
messages, and dispatches them to a midi callback. | |||||
*/ | |||||
class MidiDataConcatenator | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
MidiDataConcatenator (const int initialBufferSize) | |||||
: pendingData ((size_t) initialBufferSize), | |||||
pendingDataTime (0), pendingBytes (0), runningStatus (0) | |||||
{ | |||||
} | |||||
void reset() | |||||
{ | |||||
pendingBytes = 0; | |||||
runningStatus = 0; | |||||
pendingDataTime = 0; | |||||
} | |||||
template <typename UserDataType, typename CallbackType> | |||||
void pushMidiData (const void* inputData, int numBytes, double time, | |||||
UserDataType* input, CallbackType& callback) | |||||
{ | |||||
const uint8* d = static_cast <const uint8*> (inputData); | |||||
while (numBytes > 0) | |||||
{ | |||||
if (pendingBytes > 0 || d[0] == 0xf0) | |||||
{ | |||||
processSysex (d, numBytes, time, input, callback); | |||||
runningStatus = 0; | |||||
} | |||||
else | |||||
{ | |||||
int len = 0; | |||||
uint8 data[3]; | |||||
while (numBytes > 0) | |||||
{ | |||||
// If there's a realtime message embedded in the middle of | |||||
// the normal message, handle it now.. | |||||
if (*d >= 0xf8 && *d <= 0xfe) | |||||
{ | |||||
const MidiMessage m (*d++, time); | |||||
callback.handleIncomingMidiMessage (input, m); | |||||
--numBytes; | |||||
} | |||||
else | |||||
{ | |||||
if (len == 0 && *d < 0x80 && runningStatus >= 0x80) | |||||
data[len++] = runningStatus; | |||||
data[len++] = *d++; | |||||
--numBytes; | |||||
if (len >= MidiMessage::getMessageLengthFromFirstByte (data[0])) | |||||
break; | |||||
} | |||||
} | |||||
if (len > 0) | |||||
{ | |||||
int used = 0; | |||||
const MidiMessage m (data, len, used, 0, time); | |||||
if (used <= 0) | |||||
break; // malformed message.. | |||||
jassert (used == len); | |||||
callback.handleIncomingMidiMessage (input, m); | |||||
runningStatus = data[0]; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
private: | |||||
template <typename UserDataType, typename CallbackType> | |||||
void processSysex (const uint8*& d, int& numBytes, double time, | |||||
UserDataType* input, CallbackType& callback) | |||||
{ | |||||
if (*d == 0xf0) | |||||
{ | |||||
pendingBytes = 0; | |||||
pendingDataTime = time; | |||||
} | |||||
pendingData.ensureSize ((size_t) (pendingBytes + numBytes), false); | |||||
uint8* totalMessage = static_cast<uint8*> (pendingData.getData()); | |||||
uint8* dest = totalMessage + pendingBytes; | |||||
do | |||||
{ | |||||
if (pendingBytes > 0 && *d >= 0x80) | |||||
{ | |||||
if (*d >= 0xfa || *d == 0xf8) | |||||
{ | |||||
callback.handleIncomingMidiMessage (input, MidiMessage (*d, time)); | |||||
++d; | |||||
--numBytes; | |||||
} | |||||
else | |||||
{ | |||||
if (*d == 0xf7) | |||||
{ | |||||
*dest++ = *d++; | |||||
pendingBytes++; | |||||
--numBytes; | |||||
} | |||||
break; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
*dest++ = *d++; | |||||
pendingBytes++; | |||||
--numBytes; | |||||
} | |||||
} | |||||
while (numBytes > 0); | |||||
if (pendingBytes > 0) | |||||
{ | |||||
if (totalMessage [pendingBytes - 1] == 0xf7) | |||||
{ | |||||
callback.handleIncomingMidiMessage (input, MidiMessage (totalMessage, pendingBytes, pendingDataTime)); | |||||
pendingBytes = 0; | |||||
} | |||||
else | |||||
{ | |||||
callback.handlePartialSysexMessage (input, totalMessage, pendingBytes, pendingDataTime); | |||||
} | |||||
} | |||||
} | |||||
MemoryBlock pendingData; | |||||
double pendingDataTime; | |||||
int pendingBytes; | |||||
uint8 runningStatus; | |||||
JUCE_DECLARE_NON_COPYABLE (MidiDataConcatenator) | |||||
}; | |||||
#endif // __JUCE_MIDIDATACONCATENATOR_JUCEHEADER__ |
@@ -0,0 +1,444 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
//============================================================================== | |||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | |||||
STATICMETHOD (getMinBufferSize, "getMinBufferSize", "(III)I") \ | |||||
STATICMETHOD (getNativeOutputSampleRate, "getNativeOutputSampleRate", "(I)I") \ | |||||
METHOD (constructor, "<init>", "(IIIIII)V") \ | |||||
METHOD (getState, "getState", "()I") \ | |||||
METHOD (play, "play", "()V") \ | |||||
METHOD (stop, "stop", "()V") \ | |||||
METHOD (release, "release", "()V") \ | |||||
METHOD (flush, "flush", "()V") \ | |||||
METHOD (write, "write", "([SII)I") \ | |||||
DECLARE_JNI_CLASS (AudioTrack, "android/media/AudioTrack"); | |||||
#undef JNI_CLASS_MEMBERS | |||||
//============================================================================== | |||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | |||||
STATICMETHOD (getMinBufferSize, "getMinBufferSize", "(III)I") \ | |||||
METHOD (constructor, "<init>", "(IIIII)V") \ | |||||
METHOD (getState, "getState", "()I") \ | |||||
METHOD (startRecording, "startRecording", "()V") \ | |||||
METHOD (stop, "stop", "()V") \ | |||||
METHOD (read, "read", "([SII)I") \ | |||||
METHOD (release, "release", "()V") \ | |||||
DECLARE_JNI_CLASS (AudioRecord, "android/media/AudioRecord"); | |||||
#undef JNI_CLASS_MEMBERS | |||||
//============================================================================== | |||||
enum | |||||
{ | |||||
CHANNEL_OUT_STEREO = 12, | |||||
CHANNEL_IN_STEREO = 12, | |||||
CHANNEL_IN_MONO = 16, | |||||
ENCODING_PCM_16BIT = 2, | |||||
STREAM_MUSIC = 3, | |||||
MODE_STREAM = 1, | |||||
STATE_UNINITIALIZED = 0 | |||||
}; | |||||
const char* const javaAudioTypeName = "Android Audio"; | |||||
//============================================================================== | |||||
class AndroidAudioIODevice : public AudioIODevice, | |||||
public Thread | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
AndroidAudioIODevice (const String& deviceName) | |||||
: AudioIODevice (deviceName, javaAudioTypeName), | |||||
Thread ("audio"), | |||||
minBufferSizeOut (0), minBufferSizeIn (0), callback (0), sampleRate (0), | |||||
numClientInputChannels (0), numDeviceInputChannels (0), numDeviceInputChannelsAvailable (2), | |||||
numClientOutputChannels (0), numDeviceOutputChannels (0), | |||||
actualBufferSize (0), isRunning (false), | |||||
outputChannelBuffer (1, 1), | |||||
inputChannelBuffer (1, 1) | |||||
{ | |||||
JNIEnv* env = getEnv(); | |||||
sampleRate = env->CallStaticIntMethod (AudioTrack, AudioTrack.getNativeOutputSampleRate, MODE_STREAM); | |||||
minBufferSizeOut = (int) env->CallStaticIntMethod (AudioTrack, AudioTrack.getMinBufferSize, sampleRate, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT); | |||||
minBufferSizeIn = (int) env->CallStaticIntMethod (AudioRecord, AudioRecord.getMinBufferSize, sampleRate, CHANNEL_IN_STEREO, ENCODING_PCM_16BIT); | |||||
if (minBufferSizeIn <= 0) | |||||
{ | |||||
minBufferSizeIn = env->CallStaticIntMethod (AudioRecord, AudioRecord.getMinBufferSize, sampleRate, CHANNEL_IN_MONO, ENCODING_PCM_16BIT); | |||||
if (minBufferSizeIn > 0) | |||||
numDeviceInputChannelsAvailable = 1; | |||||
else | |||||
numDeviceInputChannelsAvailable = 0; | |||||
} | |||||
DBG ("Audio device - min buffers: " << minBufferSizeOut << ", " << minBufferSizeIn << "; " | |||||
<< sampleRate << " Hz; input chans: " << numDeviceInputChannelsAvailable); | |||||
} | |||||
~AndroidAudioIODevice() | |||||
{ | |||||
close(); | |||||
} | |||||
StringArray getOutputChannelNames() | |||||
{ | |||||
StringArray s; | |||||
s.add ("Left"); | |||||
s.add ("Right"); | |||||
return s; | |||||
} | |||||
StringArray getInputChannelNames() | |||||
{ | |||||
StringArray s; | |||||
if (numDeviceInputChannelsAvailable == 2) | |||||
{ | |||||
s.add ("Left"); | |||||
s.add ("Right"); | |||||
} | |||||
else if (numDeviceInputChannelsAvailable == 1) | |||||
{ | |||||
s.add ("Audio Input"); | |||||
} | |||||
return s; | |||||
} | |||||
int getNumSampleRates() { return 1;} | |||||
double getSampleRate (int index) { return sampleRate; } | |||||
int getDefaultBufferSize() { return 2048; } | |||||
int getNumBufferSizesAvailable() { return 50; } | |||||
int getBufferSizeSamples (int index) | |||||
{ | |||||
int n = 16; | |||||
for (int i = 0; i < index; ++i) | |||||
n += n < 64 ? 16 | |||||
: (n < 512 ? 32 | |||||
: (n < 1024 ? 64 | |||||
: (n < 2048 ? 128 : 256))); | |||||
return n; | |||||
} | |||||
String open (const BigInteger& inputChannels, | |||||
const BigInteger& outputChannels, | |||||
double requestedSampleRate, | |||||
int bufferSize) | |||||
{ | |||||
close(); | |||||
if (sampleRate != (int) requestedSampleRate) | |||||
return "Sample rate not allowed"; | |||||
lastError = String::empty; | |||||
int preferredBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize; | |||||
numDeviceInputChannels = 0; | |||||
numDeviceOutputChannels = 0; | |||||
activeOutputChans = outputChannels; | |||||
activeOutputChans.setRange (2, activeOutputChans.getHighestBit(), false); | |||||
numClientOutputChannels = activeOutputChans.countNumberOfSetBits(); | |||||
activeInputChans = inputChannels; | |||||
activeInputChans.setRange (2, activeInputChans.getHighestBit(), false); | |||||
numClientInputChannels = activeInputChans.countNumberOfSetBits(); | |||||
actualBufferSize = preferredBufferSize; | |||||
inputChannelBuffer.setSize (2, actualBufferSize); | |||||
inputChannelBuffer.clear(); | |||||
outputChannelBuffer.setSize (2, actualBufferSize); | |||||
outputChannelBuffer.clear(); | |||||
JNIEnv* env = getEnv(); | |||||
if (numClientOutputChannels > 0) | |||||
{ | |||||
numDeviceOutputChannels = 2; | |||||
outputDevice = GlobalRef (env->NewObject (AudioTrack, AudioTrack.constructor, | |||||
STREAM_MUSIC, sampleRate, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT, | |||||
(jint) (minBufferSizeOut * numDeviceOutputChannels * sizeof (int16)), MODE_STREAM)); | |||||
if (env->CallIntMethod (outputDevice, AudioTrack.getState) != STATE_UNINITIALIZED) | |||||
isRunning = true; | |||||
else | |||||
outputDevice.clear(); // failed to open the device | |||||
} | |||||
if (numClientInputChannels > 0 && numDeviceInputChannelsAvailable > 0) | |||||
{ | |||||
numDeviceInputChannels = jmin (numClientInputChannels, numDeviceInputChannelsAvailable); | |||||
inputDevice = GlobalRef (env->NewObject (AudioRecord, AudioRecord.constructor, | |||||
0 /* (default audio source) */, sampleRate, | |||||
numDeviceInputChannelsAvailable > 1 ? CHANNEL_IN_STEREO : CHANNEL_IN_MONO, | |||||
ENCODING_PCM_16BIT, | |||||
(jint) (minBufferSizeIn * numDeviceInputChannels * sizeof (int16)))); | |||||
if (env->CallIntMethod (inputDevice, AudioRecord.getState) != STATE_UNINITIALIZED) | |||||
isRunning = true; | |||||
else | |||||
inputDevice.clear(); // failed to open the device | |||||
} | |||||
if (isRunning) | |||||
{ | |||||
if (outputDevice != nullptr) | |||||
env->CallVoidMethod (outputDevice, AudioTrack.play); | |||||
if (inputDevice != nullptr) | |||||
env->CallVoidMethod (inputDevice, AudioRecord.startRecording); | |||||
startThread (8); | |||||
} | |||||
else | |||||
{ | |||||
closeDevices(); | |||||
} | |||||
return lastError; | |||||
} | |||||
void close() | |||||
{ | |||||
if (isRunning) | |||||
{ | |||||
stopThread (2000); | |||||
isRunning = false; | |||||
closeDevices(); | |||||
} | |||||
} | |||||
int getOutputLatencyInSamples() { return (minBufferSizeOut * 3) / 4; } | |||||
int getInputLatencyInSamples() { return (minBufferSizeIn * 3) / 4; } | |||||
bool isOpen() { return isRunning; } | |||||
int getCurrentBufferSizeSamples() { return actualBufferSize; } | |||||
int getCurrentBitDepth() { return 16; } | |||||
double getCurrentSampleRate() { return sampleRate; } | |||||
BigInteger getActiveOutputChannels() const { return activeOutputChans; } | |||||
BigInteger getActiveInputChannels() const { return activeInputChans; } | |||||
String getLastError() { return lastError; } | |||||
bool isPlaying() { return isRunning && callback != 0; } | |||||
void start (AudioIODeviceCallback* newCallback) | |||||
{ | |||||
if (isRunning && callback != newCallback) | |||||
{ | |||||
if (newCallback != nullptr) | |||||
newCallback->audioDeviceAboutToStart (this); | |||||
const ScopedLock sl (callbackLock); | |||||
callback = newCallback; | |||||
} | |||||
} | |||||
void stop() | |||||
{ | |||||
if (isRunning) | |||||
{ | |||||
AudioIODeviceCallback* lastCallback; | |||||
{ | |||||
const ScopedLock sl (callbackLock); | |||||
lastCallback = callback; | |||||
callback = nullptr; | |||||
} | |||||
if (lastCallback != nullptr) | |||||
lastCallback->audioDeviceStopped(); | |||||
} | |||||
} | |||||
void run() | |||||
{ | |||||
JNIEnv* env = getEnv(); | |||||
jshortArray audioBuffer = env->NewShortArray (actualBufferSize * jmax (numDeviceOutputChannels, numDeviceInputChannels)); | |||||
while (! threadShouldExit()) | |||||
{ | |||||
if (inputDevice != nullptr) | |||||
{ | |||||
jint numRead = env->CallIntMethod (inputDevice, AudioRecord.read, audioBuffer, 0, actualBufferSize * numDeviceInputChannels); | |||||
if (numRead < actualBufferSize * numDeviceInputChannels) | |||||
{ | |||||
DBG ("Audio read under-run! " << numRead); | |||||
} | |||||
jshort* const src = env->GetShortArrayElements (audioBuffer, 0); | |||||
for (int chan = 0; chan < inputChannelBuffer.getNumChannels(); ++chan) | |||||
{ | |||||
AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst> d (inputChannelBuffer.getSampleData (chan)); | |||||
if (chan < numDeviceInputChannels) | |||||
{ | |||||
AudioData::Pointer <AudioData::Int16, AudioData::NativeEndian, AudioData::Interleaved, AudioData::Const> s (src + chan, numDeviceInputChannels); | |||||
d.convertSamples (s, actualBufferSize); | |||||
} | |||||
else | |||||
{ | |||||
d.clearSamples (actualBufferSize); | |||||
} | |||||
} | |||||
env->ReleaseShortArrayElements (audioBuffer, src, 0); | |||||
} | |||||
if (threadShouldExit()) | |||||
break; | |||||
{ | |||||
const ScopedLock sl (callbackLock); | |||||
if (callback != nullptr) | |||||
{ | |||||
callback->audioDeviceIOCallback ((const float**) inputChannelBuffer.getArrayOfChannels(), numClientInputChannels, | |||||
outputChannelBuffer.getArrayOfChannels(), numClientOutputChannels, | |||||
actualBufferSize); | |||||
} | |||||
else | |||||
{ | |||||
outputChannelBuffer.clear(); | |||||
} | |||||
} | |||||
if (outputDevice != nullptr) | |||||
{ | |||||
if (threadShouldExit()) | |||||
break; | |||||
jshort* const dest = env->GetShortArrayElements (audioBuffer, 0); | |||||
for (int chan = 0; chan < numDeviceOutputChannels; ++chan) | |||||
{ | |||||
AudioData::Pointer <AudioData::Int16, AudioData::NativeEndian, AudioData::Interleaved, AudioData::NonConst> d (dest + chan, numDeviceOutputChannels); | |||||
const float* const sourceChanData = outputChannelBuffer.getSampleData (jmin (chan, outputChannelBuffer.getNumChannels() - 1)); | |||||
AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const> s (sourceChanData); | |||||
d.convertSamples (s, actualBufferSize); | |||||
} | |||||
env->ReleaseShortArrayElements (audioBuffer, dest, 0); | |||||
jint numWritten = env->CallIntMethod (outputDevice, AudioTrack.write, audioBuffer, 0, actualBufferSize * numDeviceOutputChannels); | |||||
if (numWritten < actualBufferSize * numDeviceOutputChannels) | |||||
{ | |||||
DBG ("Audio write underrun! " << numWritten); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
int minBufferSizeOut, minBufferSizeIn; | |||||
private: | |||||
//================================================================================================== | |||||
CriticalSection callbackLock; | |||||
AudioIODeviceCallback* callback; | |||||
jint sampleRate; | |||||
int numClientInputChannels, numDeviceInputChannels, numDeviceInputChannelsAvailable; | |||||
int numClientOutputChannels, numDeviceOutputChannels; | |||||
int actualBufferSize; | |||||
bool isRunning; | |||||
String lastError; | |||||
BigInteger activeOutputChans, activeInputChans; | |||||
GlobalRef outputDevice, inputDevice; | |||||
AudioSampleBuffer inputChannelBuffer, outputChannelBuffer; | |||||
void closeDevices() | |||||
{ | |||||
if (outputDevice != nullptr) | |||||
{ | |||||
outputDevice.callVoidMethod (AudioTrack.stop); | |||||
outputDevice.callVoidMethod (AudioTrack.release); | |||||
outputDevice.clear(); | |||||
} | |||||
if (inputDevice != nullptr) | |||||
{ | |||||
inputDevice.callVoidMethod (AudioRecord.stop); | |||||
inputDevice.callVoidMethod (AudioRecord.release); | |||||
inputDevice.clear(); | |||||
} | |||||
} | |||||
JUCE_DECLARE_NON_COPYABLE (AndroidAudioIODevice) | |||||
}; | |||||
//============================================================================== | |||||
class AndroidAudioIODeviceType : public AudioIODeviceType | |||||
{ | |||||
public: | |||||
AndroidAudioIODeviceType() : AudioIODeviceType (javaAudioTypeName) {} | |||||
//============================================================================== | |||||
void scanForDevices() {} | |||||
StringArray getDeviceNames (bool wantInputNames) const { return StringArray (javaAudioTypeName); } | |||||
int getDefaultDeviceIndex (bool forInput) const { return 0; } | |||||
int getIndexOfDevice (AudioIODevice* device, bool asInput) const { return device != nullptr ? 0 : -1; } | |||||
bool hasSeparateInputsAndOutputs() const { return false; } | |||||
AudioIODevice* createDevice (const String& outputDeviceName, | |||||
const String& inputDeviceName) | |||||
{ | |||||
ScopedPointer<AndroidAudioIODevice> dev; | |||||
if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty()) | |||||
{ | |||||
dev = new AndroidAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName | |||||
: inputDeviceName); | |||||
if (dev->getCurrentSampleRate() <= 0 || dev->getDefaultBufferSize() <= 0) | |||||
dev = nullptr; | |||||
} | |||||
return dev.release(); | |||||
} | |||||
private: | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AndroidAudioIODeviceType) | |||||
}; | |||||
//============================================================================== | |||||
extern bool isOpenSLAvailable(); | |||||
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android() | |||||
{ | |||||
#if JUCE_USE_ANDROID_OPENSLES | |||||
if (isOpenSLAvailable()) | |||||
return nullptr; | |||||
#endif | |||||
return new AndroidAudioIODeviceType(); | |||||
} |
@@ -0,0 +1,85 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
StringArray MidiOutput::getDevices() | |||||
{ | |||||
StringArray devices; | |||||
return devices; | |||||
} | |||||
int MidiOutput::getDefaultDeviceIndex() | |||||
{ | |||||
return 0; | |||||
} | |||||
MidiOutput* MidiOutput::openDevice (int index) | |||||
{ | |||||
return nullptr; | |||||
} | |||||
MidiOutput::~MidiOutput() | |||||
{ | |||||
} | |||||
void MidiOutput::sendMessageNow (const MidiMessage&) | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
MidiInput::MidiInput (const String& name_) | |||||
: name (name_), | |||||
internal (0) | |||||
{ | |||||
} | |||||
MidiInput::~MidiInput() | |||||
{ | |||||
} | |||||
void MidiInput::start() | |||||
{ | |||||
} | |||||
void MidiInput::stop() | |||||
{ | |||||
} | |||||
int MidiInput::getDefaultDeviceIndex() | |||||
{ | |||||
return 0; | |||||
} | |||||
StringArray MidiInput::getDevices() | |||||
{ | |||||
StringArray devs; | |||||
return devs; | |||||
} | |||||
MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) | |||||
{ | |||||
return nullptr; | |||||
} |
@@ -0,0 +1,623 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
const char* const openSLTypeName = "Android OpenSL"; | |||||
bool isOpenSLAvailable() | |||||
{ | |||||
DynamicLibrary library; | |||||
return library.open ("libOpenSLES.so"); | |||||
} | |||||
const unsigned short openSLRates[] = { 8000, 16000, 32000, 44100, 48000 }; | |||||
const unsigned short openSLBufferSizes[] = { 256, 512, 768, 1024, 1280, 1600 }; // must all be multiples of the block size | |||||
//============================================================================== | |||||
class OpenSLAudioIODevice : public AudioIODevice, | |||||
public Thread | |||||
{ | |||||
public: | |||||
OpenSLAudioIODevice (const String& deviceName) | |||||
: AudioIODevice (deviceName, openSLTypeName), | |||||
Thread ("OpenSL"), | |||||
callback (nullptr), sampleRate (0), deviceOpen (false), | |||||
inputBuffer (2, 2), outputBuffer (2, 2) | |||||
{ | |||||
// OpenSL has piss-poor support for determining latency, so the only way I can find to | |||||
// get a number for this is by asking the AudioTrack/AudioRecord classes.. | |||||
AndroidAudioIODevice javaDevice (String::empty); | |||||
// this is a total guess about how to calculate the latency, but seems to vaguely agree | |||||
// with the devices I've tested.. YMMV | |||||
inputLatency = ((javaDevice.minBufferSizeIn * 2) / 3); | |||||
outputLatency = ((javaDevice.minBufferSizeOut * 2) / 3); | |||||
const int longestLatency = jmax (inputLatency, outputLatency); | |||||
const int totalLatency = inputLatency + outputLatency; | |||||
inputLatency = ((longestLatency * inputLatency) / totalLatency) & ~15; | |||||
outputLatency = ((longestLatency * outputLatency) / totalLatency) & ~15; | |||||
} | |||||
~OpenSLAudioIODevice() | |||||
{ | |||||
close(); | |||||
} | |||||
bool openedOk() const { return engine.outputMixObject != nullptr; } | |||||
StringArray getOutputChannelNames() | |||||
{ | |||||
StringArray s; | |||||
s.add ("Left"); | |||||
s.add ("Right"); | |||||
return s; | |||||
} | |||||
StringArray getInputChannelNames() | |||||
{ | |||||
StringArray s; | |||||
s.add ("Audio Input"); | |||||
return s; | |||||
} | |||||
int getNumSampleRates() { return numElementsInArray (openSLRates); } | |||||
double getSampleRate (int index) | |||||
{ | |||||
jassert (index >= 0 && index < getNumSampleRates()); | |||||
return (int) openSLRates [index]; | |||||
} | |||||
int getDefaultBufferSize() { return 1024; } | |||||
int getNumBufferSizesAvailable() { return numElementsInArray (openSLBufferSizes); } | |||||
int getBufferSizeSamples (int index) | |||||
{ | |||||
jassert (index >= 0 && index < getNumBufferSizesAvailable()); | |||||
return (int) openSLBufferSizes [index]; | |||||
} | |||||
String open (const BigInteger& inputChannels, | |||||
const BigInteger& outputChannels, | |||||
double requestedSampleRate, | |||||
int bufferSize) | |||||
{ | |||||
close(); | |||||
lastError = String::empty; | |||||
sampleRate = (int) requestedSampleRate; | |||||
int preferredBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize; | |||||
activeOutputChans = outputChannels; | |||||
activeOutputChans.setRange (2, activeOutputChans.getHighestBit(), false); | |||||
numOutputChannels = activeOutputChans.countNumberOfSetBits(); | |||||
activeInputChans = inputChannels; | |||||
activeInputChans.setRange (1, activeInputChans.getHighestBit(), false); | |||||
numInputChannels = activeInputChans.countNumberOfSetBits(); | |||||
actualBufferSize = preferredBufferSize; | |||||
inputBuffer.setSize (jmax (1, numInputChannels), actualBufferSize); | |||||
outputBuffer.setSize (jmax (1, numOutputChannels), actualBufferSize); | |||||
outputBuffer.clear(); | |||||
recorder = engine.createRecorder (numInputChannels, sampleRate); | |||||
player = engine.createPlayer (numOutputChannels, sampleRate); | |||||
startThread (8); | |||||
deviceOpen = true; | |||||
return lastError; | |||||
} | |||||
void close() | |||||
{ | |||||
stop(); | |||||
stopThread (6000); | |||||
deviceOpen = false; | |||||
recorder = nullptr; | |||||
player = nullptr; | |||||
} | |||||
int getOutputLatencyInSamples() { return outputLatency; } | |||||
int getInputLatencyInSamples() { return inputLatency; } | |||||
bool isOpen() { return deviceOpen; } | |||||
int getCurrentBufferSizeSamples() { return actualBufferSize; } | |||||
int getCurrentBitDepth() { return 16; } | |||||
double getCurrentSampleRate() { return sampleRate; } | |||||
BigInteger getActiveOutputChannels() const { return activeOutputChans; } | |||||
BigInteger getActiveInputChannels() const { return activeInputChans; } | |||||
String getLastError() { return lastError; } | |||||
bool isPlaying() { return callback != nullptr; } | |||||
void start (AudioIODeviceCallback* newCallback) | |||||
{ | |||||
stop(); | |||||
if (deviceOpen && callback != newCallback) | |||||
{ | |||||
if (newCallback != nullptr) | |||||
newCallback->audioDeviceAboutToStart (this); | |||||
setCallback (newCallback); | |||||
} | |||||
} | |||||
void stop() | |||||
{ | |||||
if (AudioIODeviceCallback* const oldCallback = setCallback (nullptr)) | |||||
oldCallback->audioDeviceStopped(); | |||||
} | |||||
void run() | |||||
{ | |||||
if (recorder != nullptr) recorder->start(); | |||||
if (player != nullptr) player->start(); | |||||
while (! threadShouldExit()) | |||||
{ | |||||
if (player != nullptr) player->writeBuffer (outputBuffer, *this); | |||||
if (recorder != nullptr) recorder->readNextBlock (inputBuffer, *this); | |||||
const ScopedLock sl (callbackLock); | |||||
if (callback != nullptr) | |||||
{ | |||||
callback->audioDeviceIOCallback (numInputChannels > 0 ? (const float**) inputBuffer.getArrayOfChannels() : nullptr, | |||||
numInputChannels, | |||||
numOutputChannels > 0 ? outputBuffer.getArrayOfChannels() : nullptr, | |||||
numOutputChannels, | |||||
actualBufferSize); | |||||
} | |||||
else | |||||
{ | |||||
outputBuffer.clear(); | |||||
} | |||||
} | |||||
} | |||||
private: | |||||
//================================================================================================== | |||||
CriticalSection callbackLock; | |||||
AudioIODeviceCallback* callback; | |||||
int actualBufferSize, sampleRate; | |||||
int inputLatency, outputLatency; | |||||
bool deviceOpen; | |||||
String lastError; | |||||
BigInteger activeOutputChans, activeInputChans; | |||||
int numInputChannels, numOutputChannels; | |||||
AudioSampleBuffer inputBuffer, outputBuffer; | |||||
struct Player; | |||||
struct Recorder; | |||||
AudioIODeviceCallback* setCallback (AudioIODeviceCallback* const newCallback) | |||||
{ | |||||
const ScopedLock sl (callbackLock); | |||||
AudioIODeviceCallback* const oldCallback = callback; | |||||
callback = newCallback; | |||||
return oldCallback; | |||||
} | |||||
//================================================================================================== | |||||
struct Engine | |||||
{ | |||||
Engine() | |||||
: engineObject (nullptr), engineInterface (nullptr), outputMixObject (nullptr) | |||||
{ | |||||
if (library.open ("libOpenSLES.so")) | |||||
{ | |||||
typedef SLresult (*CreateEngineFunc) (SLObjectItf*, SLuint32, const SLEngineOption*, SLuint32, const SLInterfaceID*, const SLboolean*); | |||||
if (CreateEngineFunc createEngine = (CreateEngineFunc) library.getFunction ("slCreateEngine")) | |||||
{ | |||||
check (createEngine (&engineObject, 0, nullptr, 0, nullptr, nullptr)); | |||||
SLInterfaceID* SL_IID_ENGINE = (SLInterfaceID*) library.getFunction ("SL_IID_ENGINE"); | |||||
SL_IID_ANDROIDSIMPLEBUFFERQUEUE = (SLInterfaceID*) library.getFunction ("SL_IID_ANDROIDSIMPLEBUFFERQUEUE"); | |||||
SL_IID_PLAY = (SLInterfaceID*) library.getFunction ("SL_IID_PLAY"); | |||||
SL_IID_RECORD = (SLInterfaceID*) library.getFunction ("SL_IID_RECORD"); | |||||
check ((*engineObject)->Realize (engineObject, SL_BOOLEAN_FALSE)); | |||||
check ((*engineObject)->GetInterface (engineObject, *SL_IID_ENGINE, &engineInterface)); | |||||
check ((*engineInterface)->CreateOutputMix (engineInterface, &outputMixObject, 0, nullptr, nullptr)); | |||||
check ((*outputMixObject)->Realize (outputMixObject, SL_BOOLEAN_FALSE)); | |||||
} | |||||
} | |||||
} | |||||
~Engine() | |||||
{ | |||||
if (outputMixObject != nullptr) (*outputMixObject)->Destroy (outputMixObject); | |||||
if (engineObject != nullptr) (*engineObject)->Destroy (engineObject); | |||||
} | |||||
Player* createPlayer (const int numChannels, const int sampleRate) | |||||
{ | |||||
if (numChannels <= 0) | |||||
return nullptr; | |||||
ScopedPointer<Player> player (new Player (numChannels, sampleRate, *this)); | |||||
return player->openedOk() ? player.release() : nullptr; | |||||
} | |||||
Recorder* createRecorder (const int numChannels, const int sampleRate) | |||||
{ | |||||
if (numChannels <= 0) | |||||
return nullptr; | |||||
ScopedPointer<Recorder> recorder (new Recorder (numChannels, sampleRate, *this)); | |||||
return recorder->openedOk() ? recorder.release() : nullptr; | |||||
} | |||||
SLObjectItf engineObject; | |||||
SLEngineItf engineInterface; | |||||
SLObjectItf outputMixObject; | |||||
SLInterfaceID* SL_IID_ANDROIDSIMPLEBUFFERQUEUE; | |||||
SLInterfaceID* SL_IID_PLAY; | |||||
SLInterfaceID* SL_IID_RECORD; | |||||
private: | |||||
DynamicLibrary library; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Engine) | |||||
}; | |||||
//================================================================================================== | |||||
struct BufferList | |||||
{ | |||||
BufferList (const int numChannels_) | |||||
: numChannels (numChannels_), bufferSpace (numChannels_ * numSamples * numBuffers), nextBlock (0) | |||||
{ | |||||
} | |||||
int16* waitForFreeBuffer (Thread& threadToCheck) | |||||
{ | |||||
while (numBlocksOut.get() == numBuffers) | |||||
{ | |||||
dataArrived.wait (1); | |||||
if (threadToCheck.threadShouldExit()) | |||||
return nullptr; | |||||
} | |||||
return getNextBuffer(); | |||||
} | |||||
int16* getNextBuffer() | |||||
{ | |||||
if (++nextBlock == numBuffers) | |||||
nextBlock = 0; | |||||
return bufferSpace + nextBlock * numChannels * numSamples; | |||||
} | |||||
void bufferReturned() { --numBlocksOut; dataArrived.signal(); } | |||||
void bufferSent() { ++numBlocksOut; dataArrived.signal(); } | |||||
int getBufferSizeBytes() const { return numChannels * numSamples * sizeof (int16); } | |||||
const int numChannels; | |||||
enum { numSamples = 256, numBuffers = 16 }; | |||||
private: | |||||
HeapBlock<int16> bufferSpace; | |||||
int nextBlock; | |||||
Atomic<int> numBlocksOut; | |||||
WaitableEvent dataArrived; | |||||
}; | |||||
//================================================================================================== | |||||
struct Player | |||||
{ | |||||
Player (int numChannels, int sampleRate, Engine& engine) | |||||
: playerObject (nullptr), playerPlay (nullptr), playerBufferQueue (nullptr), | |||||
bufferList (numChannels) | |||||
{ | |||||
jassert (numChannels == 2); | |||||
SLDataFormat_PCM pcmFormat = | |||||
{ | |||||
SL_DATAFORMAT_PCM, | |||||
numChannels, | |||||
sampleRate * 1000, // (sample rate units are millihertz) | |||||
SL_PCMSAMPLEFORMAT_FIXED_16, | |||||
SL_PCMSAMPLEFORMAT_FIXED_16, | |||||
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, | |||||
SL_BYTEORDER_LITTLEENDIAN | |||||
}; | |||||
SLDataLocator_AndroidSimpleBufferQueue bufferQueue = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, bufferList.numBuffers }; | |||||
SLDataSource audioSrc = { &bufferQueue, &pcmFormat }; | |||||
SLDataLocator_OutputMix outputMix = { SL_DATALOCATOR_OUTPUTMIX, engine.outputMixObject }; | |||||
SLDataSink audioSink = { &outputMix, nullptr }; | |||||
// (SL_IID_BUFFERQUEUE is not guaranteed to remain future-proof, so use SL_IID_ANDROIDSIMPLEBUFFERQUEUE) | |||||
const SLInterfaceID interfaceIDs[] = { *engine.SL_IID_ANDROIDSIMPLEBUFFERQUEUE }; | |||||
const SLboolean flags[] = { SL_BOOLEAN_TRUE }; | |||||
check ((*engine.engineInterface)->CreateAudioPlayer (engine.engineInterface, &playerObject, &audioSrc, &audioSink, | |||||
1, interfaceIDs, flags)); | |||||
check ((*playerObject)->Realize (playerObject, SL_BOOLEAN_FALSE)); | |||||
check ((*playerObject)->GetInterface (playerObject, *engine.SL_IID_PLAY, &playerPlay)); | |||||
check ((*playerObject)->GetInterface (playerObject, *engine.SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &playerBufferQueue)); | |||||
check ((*playerBufferQueue)->RegisterCallback (playerBufferQueue, staticCallback, this)); | |||||
} | |||||
~Player() | |||||
{ | |||||
if (playerPlay != nullptr) | |||||
check ((*playerPlay)->SetPlayState (playerPlay, SL_PLAYSTATE_STOPPED)); | |||||
if (playerBufferQueue != nullptr) | |||||
check ((*playerBufferQueue)->Clear (playerBufferQueue)); | |||||
if (playerObject != nullptr) | |||||
(*playerObject)->Destroy (playerObject); | |||||
} | |||||
bool openedOk() const noexcept { return playerBufferQueue != nullptr; } | |||||
void start() | |||||
{ | |||||
jassert (openedOk()); | |||||
check ((*playerPlay)->SetPlayState (playerPlay, SL_PLAYSTATE_PLAYING)); | |||||
} | |||||
void writeBuffer (const AudioSampleBuffer& buffer, Thread& thread) | |||||
{ | |||||
jassert (buffer.getNumChannels() == bufferList.numChannels); | |||||
jassert (buffer.getNumSamples() < bufferList.numSamples * bufferList.numBuffers); | |||||
int offset = 0; | |||||
int numSamples = buffer.getNumSamples(); | |||||
while (numSamples > 0) | |||||
{ | |||||
int16* const destBuffer = bufferList.waitForFreeBuffer (thread); | |||||
if (destBuffer == nullptr) | |||||
break; | |||||
for (int i = 0; i < bufferList.numChannels; ++i) | |||||
{ | |||||
typedef AudioData::Pointer <AudioData::Int16, AudioData::LittleEndian, AudioData::Interleaved, AudioData::NonConst> DstSampleType; | |||||
typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const> SrcSampleType; | |||||
DstSampleType dstData (destBuffer + i, bufferList.numChannels); | |||||
SrcSampleType srcData (buffer.getSampleData (i, offset)); | |||||
dstData.convertSamples (srcData, bufferList.numSamples); | |||||
} | |||||
check ((*playerBufferQueue)->Enqueue (playerBufferQueue, destBuffer, bufferList.getBufferSizeBytes())); | |||||
bufferList.bufferSent(); | |||||
numSamples -= bufferList.numSamples; | |||||
offset += bufferList.numSamples; | |||||
} | |||||
} | |||||
private: | |||||
SLObjectItf playerObject; | |||||
SLPlayItf playerPlay; | |||||
SLAndroidSimpleBufferQueueItf playerBufferQueue; | |||||
BufferList bufferList; | |||||
static void staticCallback (SLAndroidSimpleBufferQueueItf queue, void* context) | |||||
{ | |||||
jassert (queue == static_cast <Player*> (context)->playerBufferQueue); (void) queue; | |||||
static_cast <Player*> (context)->bufferList.bufferReturned(); | |||||
} | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Player) | |||||
}; | |||||
//================================================================================================== | |||||
struct Recorder | |||||
{ | |||||
Recorder (int numChannels, int sampleRate, Engine& engine) | |||||
: recorderObject (nullptr), recorderRecord (nullptr), recorderBufferQueue (nullptr), | |||||
bufferList (numChannels) | |||||
{ | |||||
jassert (numChannels == 1); // STEREO doesn't always work!! | |||||
SLDataFormat_PCM pcmFormat = | |||||
{ | |||||
SL_DATAFORMAT_PCM, | |||||
numChannels, | |||||
sampleRate * 1000, // (sample rate units are millihertz) | |||||
SL_PCMSAMPLEFORMAT_FIXED_16, | |||||
SL_PCMSAMPLEFORMAT_FIXED_16, | |||||
(numChannels == 1) ? SL_SPEAKER_FRONT_CENTER : (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT), | |||||
SL_BYTEORDER_LITTLEENDIAN | |||||
}; | |||||
SLDataLocator_IODevice ioDevice = { SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, nullptr }; | |||||
SLDataSource audioSrc = { &ioDevice, nullptr }; | |||||
SLDataLocator_AndroidSimpleBufferQueue bufferQueue = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, bufferList.numBuffers }; | |||||
SLDataSink audioSink = { &bufferQueue, &pcmFormat }; | |||||
const SLInterfaceID interfaceIDs[] = { *engine.SL_IID_ANDROIDSIMPLEBUFFERQUEUE }; | |||||
const SLboolean flags[] = { SL_BOOLEAN_TRUE }; | |||||
if (check ((*engine.engineInterface)->CreateAudioRecorder (engine.engineInterface, &recorderObject, &audioSrc, | |||||
&audioSink, 1, interfaceIDs, flags))) | |||||
{ | |||||
if (check ((*recorderObject)->Realize (recorderObject, SL_BOOLEAN_FALSE))) | |||||
{ | |||||
check ((*recorderObject)->GetInterface (recorderObject, *engine.SL_IID_RECORD, &recorderRecord)); | |||||
check ((*recorderObject)->GetInterface (recorderObject, *engine.SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &recorderBufferQueue)); | |||||
check ((*recorderBufferQueue)->RegisterCallback (recorderBufferQueue, staticCallback, this)); | |||||
check ((*recorderRecord)->SetRecordState (recorderRecord, SL_RECORDSTATE_STOPPED)); | |||||
for (int i = bufferList.numBuffers; --i >= 0;) | |||||
{ | |||||
int16* const buffer = bufferList.getNextBuffer(); | |||||
jassert (buffer != nullptr); | |||||
enqueueBuffer (buffer); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
~Recorder() | |||||
{ | |||||
if (recorderRecord != nullptr) | |||||
check ((*recorderRecord)->SetRecordState (recorderRecord, SL_RECORDSTATE_STOPPED)); | |||||
if (recorderBufferQueue != nullptr) | |||||
check ((*recorderBufferQueue)->Clear (recorderBufferQueue)); | |||||
if (recorderObject != nullptr) | |||||
(*recorderObject)->Destroy (recorderObject); | |||||
} | |||||
bool openedOk() const noexcept { return recorderBufferQueue != nullptr; } | |||||
void start() | |||||
{ | |||||
jassert (openedOk()); | |||||
check ((*recorderRecord)->SetRecordState (recorderRecord, SL_RECORDSTATE_RECORDING)); | |||||
} | |||||
void readNextBlock (AudioSampleBuffer& buffer, Thread& thread) | |||||
{ | |||||
jassert (buffer.getNumChannels() == bufferList.numChannels); | |||||
jassert (buffer.getNumSamples() < bufferList.numSamples * bufferList.numBuffers); | |||||
jassert ((buffer.getNumSamples() % bufferList.numSamples) == 0); | |||||
int offset = 0; | |||||
int numSamples = buffer.getNumSamples(); | |||||
while (numSamples > 0) | |||||
{ | |||||
int16* const srcBuffer = bufferList.waitForFreeBuffer (thread); | |||||
if (srcBuffer == nullptr) | |||||
break; | |||||
for (int i = 0; i < bufferList.numChannels; ++i) | |||||
{ | |||||
typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst> DstSampleType; | |||||
typedef AudioData::Pointer <AudioData::Int16, AudioData::LittleEndian, AudioData::Interleaved, AudioData::Const> SrcSampleType; | |||||
DstSampleType dstData (buffer.getSampleData (i, offset)); | |||||
SrcSampleType srcData (srcBuffer + i, bufferList.numChannels); | |||||
dstData.convertSamples (srcData, bufferList.numSamples); | |||||
} | |||||
enqueueBuffer (srcBuffer); | |||||
numSamples -= bufferList.numSamples; | |||||
offset += bufferList.numSamples; | |||||
} | |||||
} | |||||
private: | |||||
SLObjectItf recorderObject; | |||||
SLRecordItf recorderRecord; | |||||
SLAndroidSimpleBufferQueueItf recorderBufferQueue; | |||||
BufferList bufferList; | |||||
void enqueueBuffer (int16* buffer) | |||||
{ | |||||
check ((*recorderBufferQueue)->Enqueue (recorderBufferQueue, buffer, bufferList.getBufferSizeBytes())); | |||||
bufferList.bufferSent(); | |||||
} | |||||
static void staticCallback (SLAndroidSimpleBufferQueueItf queue, void* context) | |||||
{ | |||||
jassert (queue == static_cast <Recorder*> (context)->recorderBufferQueue); (void) queue; | |||||
static_cast <Recorder*> (context)->bufferList.bufferReturned(); | |||||
} | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Recorder) | |||||
}; | |||||
//============================================================================== | |||||
Engine engine; | |||||
ScopedPointer<Player> player; | |||||
ScopedPointer<Recorder> recorder; | |||||
//============================================================================== | |||||
static bool check (const SLresult result) | |||||
{ | |||||
jassert (result == SL_RESULT_SUCCESS); | |||||
return result == SL_RESULT_SUCCESS; | |||||
} | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenSLAudioIODevice) | |||||
}; | |||||
//============================================================================== | |||||
class OpenSLAudioDeviceType : public AudioIODeviceType | |||||
{ | |||||
public: | |||||
OpenSLAudioDeviceType() : AudioIODeviceType (openSLTypeName) {} | |||||
//============================================================================== | |||||
void scanForDevices() {} | |||||
StringArray getDeviceNames (bool wantInputNames) const { return StringArray (openSLTypeName); } | |||||
int getDefaultDeviceIndex (bool forInput) const { return 0; } | |||||
int getIndexOfDevice (AudioIODevice* device, bool asInput) const { return device != nullptr ? 0 : -1; } | |||||
bool hasSeparateInputsAndOutputs() const { return false; } | |||||
AudioIODevice* createDevice (const String& outputDeviceName, | |||||
const String& inputDeviceName) | |||||
{ | |||||
ScopedPointer<OpenSLAudioIODevice> dev; | |||||
if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty()) | |||||
{ | |||||
dev = new OpenSLAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName | |||||
: inputDeviceName); | |||||
if (! dev->openedOk()) | |||||
dev = nullptr; | |||||
} | |||||
return dev.release(); | |||||
} | |||||
private: | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenSLAudioDeviceType) | |||||
}; | |||||
//============================================================================== | |||||
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES() | |||||
{ | |||||
return isOpenSLAvailable() ? new OpenSLAudioDeviceType() : nullptr; | |||||
} |
@@ -0,0 +1,547 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
class iOSAudioIODevice : public AudioIODevice | |||||
{ | |||||
public: | |||||
iOSAudioIODevice (const String& deviceName) | |||||
: AudioIODevice (deviceName, "Audio"), | |||||
actualBufferSize (0), | |||||
isRunning (false), | |||||
audioUnit (0), | |||||
callback (nullptr), | |||||
floatData (1, 2) | |||||
{ | |||||
getSessionHolder().activeDevices.add (this); | |||||
numInputChannels = 2; | |||||
numOutputChannels = 2; | |||||
preferredBufferSize = 0; | |||||
updateDeviceInfo(); | |||||
} | |||||
~iOSAudioIODevice() | |||||
{ | |||||
getSessionHolder().activeDevices.removeFirstMatchingValue (this); | |||||
close(); | |||||
} | |||||
StringArray getOutputChannelNames() | |||||
{ | |||||
StringArray s; | |||||
s.add ("Left"); | |||||
s.add ("Right"); | |||||
return s; | |||||
} | |||||
StringArray getInputChannelNames() | |||||
{ | |||||
StringArray s; | |||||
if (audioInputIsAvailable) | |||||
{ | |||||
s.add ("Left"); | |||||
s.add ("Right"); | |||||
} | |||||
return s; | |||||
} | |||||
int getNumSampleRates() { return 1; } | |||||
double getSampleRate (int index) { return sampleRate; } | |||||
int getNumBufferSizesAvailable() { return 6; } | |||||
int getBufferSizeSamples (int index) { return 1 << (jlimit (0, 5, index) + 6); } | |||||
int getDefaultBufferSize() { return 1024; } | |||||
String open (const BigInteger& inputChannels, | |||||
const BigInteger& outputChannels, | |||||
double sampleRate, | |||||
int bufferSize) | |||||
{ | |||||
close(); | |||||
lastError = String::empty; | |||||
preferredBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize; | |||||
// xxx set up channel mapping | |||||
activeOutputChans = outputChannels; | |||||
activeOutputChans.setRange (2, activeOutputChans.getHighestBit(), false); | |||||
numOutputChannels = activeOutputChans.countNumberOfSetBits(); | |||||
monoOutputChannelNumber = activeOutputChans.findNextSetBit (0); | |||||
activeInputChans = inputChannels; | |||||
activeInputChans.setRange (2, activeInputChans.getHighestBit(), false); | |||||
numInputChannels = activeInputChans.countNumberOfSetBits(); | |||||
monoInputChannelNumber = activeInputChans.findNextSetBit (0); | |||||
AudioSessionSetActive (true); | |||||
UInt32 audioCategory = kAudioSessionCategory_MediaPlayback; | |||||
if (numInputChannels > 0 && audioInputIsAvailable) | |||||
{ | |||||
audioCategory = kAudioSessionCategory_PlayAndRecord; | |||||
UInt32 allowBluetoothInput = 1; | |||||
AudioSessionSetProperty (kAudioSessionProperty_OverrideCategoryEnableBluetoothInput, | |||||
sizeof (allowBluetoothInput), &allowBluetoothInput); | |||||
} | |||||
AudioSessionSetProperty (kAudioSessionProperty_AudioCategory, sizeof (audioCategory), &audioCategory); | |||||
AudioSessionAddPropertyListener (kAudioSessionProperty_AudioRouteChange, routingChangedStatic, this); | |||||
fixAudioRouteIfSetToReceiver(); | |||||
updateDeviceInfo(); | |||||
Float32 bufferDuration = preferredBufferSize / sampleRate; | |||||
AudioSessionSetProperty (kAudioSessionProperty_PreferredHardwareIOBufferDuration, sizeof (bufferDuration), &bufferDuration); | |||||
actualBufferSize = preferredBufferSize; | |||||
prepareFloatBuffers(); | |||||
isRunning = true; | |||||
routingChanged (nullptr); // creates and starts the AU | |||||
lastError = audioUnit != 0 ? "" : "Couldn't open the device"; | |||||
return lastError; | |||||
} | |||||
void close() | |||||
{ | |||||
if (isRunning) | |||||
{ | |||||
isRunning = false; | |||||
AudioSessionRemovePropertyListenerWithUserData (kAudioSessionProperty_AudioRouteChange, routingChangedStatic, this); | |||||
AudioSessionSetActive (false); | |||||
if (audioUnit != 0) | |||||
{ | |||||
AudioComponentInstanceDispose (audioUnit); | |||||
audioUnit = 0; | |||||
} | |||||
} | |||||
} | |||||
bool isOpen() { return isRunning; } | |||||
int getCurrentBufferSizeSamples() { return actualBufferSize; } | |||||
double getCurrentSampleRate() { return sampleRate; } | |||||
int getCurrentBitDepth() { return 16; } | |||||
BigInteger getActiveOutputChannels() const { return activeOutputChans; } | |||||
BigInteger getActiveInputChannels() const { return activeInputChans; } | |||||
int getOutputLatencyInSamples() { return 0; } //xxx | |||||
int getInputLatencyInSamples() { return 0; } //xxx | |||||
void start (AudioIODeviceCallback* newCallback) | |||||
{ | |||||
if (isRunning && callback != newCallback) | |||||
{ | |||||
if (newCallback != nullptr) | |||||
newCallback->audioDeviceAboutToStart (this); | |||||
const ScopedLock sl (callbackLock); | |||||
callback = newCallback; | |||||
} | |||||
} | |||||
void stop() | |||||
{ | |||||
if (isRunning) | |||||
{ | |||||
AudioIODeviceCallback* lastCallback; | |||||
{ | |||||
const ScopedLock sl (callbackLock); | |||||
lastCallback = callback; | |||||
callback = nullptr; | |||||
} | |||||
if (lastCallback != nullptr) | |||||
lastCallback->audioDeviceStopped(); | |||||
} | |||||
} | |||||
bool isPlaying() { return isRunning && callback != nullptr; } | |||||
String getLastError() { return lastError; } | |||||
private: | |||||
//================================================================================================== | |||||
CriticalSection callbackLock; | |||||
Float64 sampleRate; | |||||
int numInputChannels, numOutputChannels; | |||||
int preferredBufferSize, actualBufferSize; | |||||
bool isRunning; | |||||
String lastError; | |||||
AudioStreamBasicDescription format; | |||||
AudioUnit audioUnit; | |||||
UInt32 audioInputIsAvailable; | |||||
AudioIODeviceCallback* callback; | |||||
BigInteger activeOutputChans, activeInputChans; | |||||
AudioSampleBuffer floatData; | |||||
float* inputChannels[3]; | |||||
float* outputChannels[3]; | |||||
bool monoInputChannelNumber, monoOutputChannelNumber; | |||||
void prepareFloatBuffers() | |||||
{ | |||||
floatData.setSize (numInputChannels + numOutputChannels, actualBufferSize); | |||||
zeromem (inputChannels, sizeof (inputChannels)); | |||||
zeromem (outputChannels, sizeof (outputChannels)); | |||||
for (int i = 0; i < numInputChannels; ++i) | |||||
inputChannels[i] = floatData.getSampleData (i); | |||||
for (int i = 0; i < numOutputChannels; ++i) | |||||
outputChannels[i] = floatData.getSampleData (i + numInputChannels); | |||||
} | |||||
//================================================================================================== | |||||
OSStatus process (AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time, | |||||
const UInt32 numFrames, AudioBufferList* data) | |||||
{ | |||||
OSStatus err = noErr; | |||||
if (audioInputIsAvailable && numInputChannels > 0) | |||||
err = AudioUnitRender (audioUnit, flags, time, 1, numFrames, data); | |||||
const ScopedLock sl (callbackLock); | |||||
if (callback != nullptr) | |||||
{ | |||||
if (audioInputIsAvailable && numInputChannels > 0) | |||||
{ | |||||
short* shortData = (short*) data->mBuffers[0].mData; | |||||
if (numInputChannels >= 2) | |||||
{ | |||||
for (UInt32 i = 0; i < numFrames; ++i) | |||||
{ | |||||
inputChannels[0][i] = *shortData++ * (1.0f / 32768.0f); | |||||
inputChannels[1][i] = *shortData++ * (1.0f / 32768.0f); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
if (monoInputChannelNumber > 0) | |||||
++shortData; | |||||
for (UInt32 i = 0; i < numFrames; ++i) | |||||
{ | |||||
inputChannels[0][i] = *shortData++ * (1.0f / 32768.0f); | |||||
++shortData; | |||||
} | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
for (int i = numInputChannels; --i >= 0;) | |||||
zeromem (inputChannels[i], sizeof (float) * numFrames); | |||||
} | |||||
callback->audioDeviceIOCallback ((const float**) inputChannels, numInputChannels, | |||||
outputChannels, numOutputChannels, (int) numFrames); | |||||
short* shortData = (short*) data->mBuffers[0].mData; | |||||
int n = 0; | |||||
if (numOutputChannels >= 2) | |||||
{ | |||||
for (UInt32 i = 0; i < numFrames; ++i) | |||||
{ | |||||
shortData [n++] = (short) (outputChannels[0][i] * 32767.0f); | |||||
shortData [n++] = (short) (outputChannels[1][i] * 32767.0f); | |||||
} | |||||
} | |||||
else if (numOutputChannels == 1) | |||||
{ | |||||
for (UInt32 i = 0; i < numFrames; ++i) | |||||
{ | |||||
const short s = (short) (outputChannels[monoOutputChannelNumber][i] * 32767.0f); | |||||
shortData [n++] = s; | |||||
shortData [n++] = s; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
zeromem (data->mBuffers[0].mData, 2 * sizeof (short) * numFrames); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
zeromem (data->mBuffers[0].mData, 2 * sizeof (short) * numFrames); | |||||
} | |||||
return err; | |||||
} | |||||
void updateDeviceInfo() | |||||
{ | |||||
UInt32 size = sizeof (sampleRate); | |||||
AudioSessionGetProperty (kAudioSessionProperty_CurrentHardwareSampleRate, &size, &sampleRate); | |||||
size = sizeof (audioInputIsAvailable); | |||||
AudioSessionGetProperty (kAudioSessionProperty_AudioInputAvailable, &size, &audioInputIsAvailable); | |||||
} | |||||
void routingChanged (const void* propertyValue) | |||||
{ | |||||
if (! isRunning) | |||||
return; | |||||
if (propertyValue != nullptr) | |||||
{ | |||||
CFDictionaryRef routeChangeDictionary = (CFDictionaryRef) propertyValue; | |||||
CFNumberRef routeChangeReasonRef = (CFNumberRef) CFDictionaryGetValue (routeChangeDictionary, | |||||
CFSTR (kAudioSession_AudioRouteChangeKey_Reason)); | |||||
SInt32 routeChangeReason; | |||||
CFNumberGetValue (routeChangeReasonRef, kCFNumberSInt32Type, &routeChangeReason); | |||||
if (routeChangeReason == kAudioSessionRouteChangeReason_OldDeviceUnavailable) | |||||
{ | |||||
const ScopedLock sl (callbackLock); | |||||
if (callback != nullptr) | |||||
callback->audioDeviceError ("Old device unavailable"); | |||||
} | |||||
} | |||||
updateDeviceInfo(); | |||||
createAudioUnit(); | |||||
AudioSessionSetActive (true); | |||||
if (audioUnit != 0) | |||||
{ | |||||
UInt32 formatSize = sizeof (format); | |||||
AudioUnitGetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, &formatSize); | |||||
Float32 bufferDuration = preferredBufferSize / sampleRate; | |||||
UInt32 bufferDurationSize = sizeof (bufferDuration); | |||||
AudioSessionGetProperty (kAudioSessionProperty_CurrentHardwareIOBufferDuration, &bufferDurationSize, &bufferDurationSize); | |||||
actualBufferSize = (int) (sampleRate * bufferDuration + 0.5); | |||||
AudioOutputUnitStart (audioUnit); | |||||
} | |||||
} | |||||
//================================================================================================== | |||||
struct AudioSessionHolder | |||||
{ | |||||
AudioSessionHolder() | |||||
{ | |||||
AudioSessionInitialize (0, 0, interruptionListenerCallback, this); | |||||
} | |||||
static void interruptionListenerCallback (void* client, UInt32 interruptionType) | |||||
{ | |||||
const Array <iOSAudioIODevice*>& activeDevices = static_cast <AudioSessionHolder*> (client)->activeDevices; | |||||
for (int i = activeDevices.size(); --i >= 0;) | |||||
activeDevices.getUnchecked(i)->interruptionListener (interruptionType); | |||||
} | |||||
Array <iOSAudioIODevice*> activeDevices; | |||||
}; | |||||
static AudioSessionHolder& getSessionHolder() | |||||
{ | |||||
static AudioSessionHolder audioSessionHolder; | |||||
return audioSessionHolder; | |||||
} | |||||
void interruptionListener (const UInt32 interruptionType) | |||||
{ | |||||
if (interruptionType == kAudioSessionBeginInterruption) | |||||
{ | |||||
close(); | |||||
const ScopedLock sl (callbackLock); | |||||
if (callback != nullptr) | |||||
callback->audioDeviceError ("iOS audio session interruption"); | |||||
} | |||||
if (interruptionType == kAudioSessionEndInterruption) | |||||
{ | |||||
isRunning = true; | |||||
AudioSessionSetActive (true); | |||||
AudioOutputUnitStart (audioUnit); | |||||
} | |||||
} | |||||
//================================================================================================== | |||||
static OSStatus processStatic (void* client, AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time, | |||||
UInt32 /*busNumber*/, UInt32 numFrames, AudioBufferList* data) | |||||
{ | |||||
return static_cast <iOSAudioIODevice*> (client)->process (flags, time, numFrames, data); | |||||
} | |||||
static void routingChangedStatic (void* client, AudioSessionPropertyID, UInt32 /*inDataSize*/, const void* propertyValue) | |||||
{ | |||||
static_cast <iOSAudioIODevice*> (client)->routingChanged (propertyValue); | |||||
} | |||||
//================================================================================================== | |||||
void resetFormat (const int numChannels) noexcept | |||||
{ | |||||
zerostruct (format); | |||||
format.mFormatID = kAudioFormatLinearPCM; | |||||
format.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked | kAudioFormatFlagsNativeEndian; | |||||
format.mBitsPerChannel = 8 * sizeof (short); | |||||
format.mChannelsPerFrame = numChannels; | |||||
format.mFramesPerPacket = 1; | |||||
format.mBytesPerFrame = format.mBytesPerPacket = numChannels * sizeof (short); | |||||
} | |||||
bool createAudioUnit() | |||||
{ | |||||
if (audioUnit != 0) | |||||
{ | |||||
AudioComponentInstanceDispose (audioUnit); | |||||
audioUnit = 0; | |||||
} | |||||
resetFormat (2); | |||||
AudioComponentDescription desc; | |||||
desc.componentType = kAudioUnitType_Output; | |||||
desc.componentSubType = kAudioUnitSubType_RemoteIO; | |||||
desc.componentManufacturer = kAudioUnitManufacturer_Apple; | |||||
desc.componentFlags = 0; | |||||
desc.componentFlagsMask = 0; | |||||
AudioComponent comp = AudioComponentFindNext (0, &desc); | |||||
AudioComponentInstanceNew (comp, &audioUnit); | |||||
if (audioUnit == 0) | |||||
return false; | |||||
if (numInputChannels > 0) | |||||
{ | |||||
const UInt32 one = 1; | |||||
AudioUnitSetProperty (audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &one, sizeof (one)); | |||||
} | |||||
{ | |||||
AudioChannelLayout layout; | |||||
layout.mChannelBitmap = 0; | |||||
layout.mNumberChannelDescriptions = 0; | |||||
layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo; | |||||
AudioUnitSetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Input, 0, &layout, sizeof (layout)); | |||||
AudioUnitSetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Output, 0, &layout, sizeof (layout)); | |||||
} | |||||
{ | |||||
AURenderCallbackStruct inputProc; | |||||
inputProc.inputProc = processStatic; | |||||
inputProc.inputProcRefCon = this; | |||||
AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &inputProc, sizeof (inputProc)); | |||||
} | |||||
AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, sizeof (format)); | |||||
AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, sizeof (format)); | |||||
AudioUnitInitialize (audioUnit); | |||||
return true; | |||||
} | |||||
// If the routing is set to go through the receiver (i.e. the speaker, but quiet), this re-routes it | |||||
// to make it loud. Needed because by default when using an input + output, the output is kept quiet. | |||||
static void fixAudioRouteIfSetToReceiver() | |||||
{ | |||||
CFStringRef audioRoute = 0; | |||||
UInt32 propertySize = sizeof (audioRoute); | |||||
if (AudioSessionGetProperty (kAudioSessionProperty_AudioRoute, &propertySize, &audioRoute) == noErr) | |||||
{ | |||||
NSString* route = (NSString*) audioRoute; | |||||
//DBG ("audio route: " + nsStringToJuce (route)); | |||||
if ([route hasPrefix: @"Receiver"]) | |||||
{ | |||||
UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_Speaker; | |||||
AudioSessionSetProperty (kAudioSessionProperty_OverrideAudioRoute, sizeof (audioRouteOverride), &audioRouteOverride); | |||||
} | |||||
CFRelease (audioRoute); | |||||
} | |||||
} | |||||
JUCE_DECLARE_NON_COPYABLE (iOSAudioIODevice) | |||||
}; | |||||
//============================================================================== | |||||
class iOSAudioIODeviceType : public AudioIODeviceType | |||||
{ | |||||
public: | |||||
iOSAudioIODeviceType() : AudioIODeviceType ("iOS Audio") | |||||
{ | |||||
} | |||||
void scanForDevices() {} | |||||
StringArray getDeviceNames (bool wantInputNames) const | |||||
{ | |||||
return StringArray ("iOS Audio"); | |||||
} | |||||
int getDefaultDeviceIndex (bool forInput) const | |||||
{ | |||||
return 0; | |||||
} | |||||
int getIndexOfDevice (AudioIODevice* device, bool asInput) const | |||||
{ | |||||
return device != nullptr ? 0 : -1; | |||||
} | |||||
bool hasSeparateInputsAndOutputs() const { return false; } | |||||
AudioIODevice* createDevice (const String& outputDeviceName, | |||||
const String& inputDeviceName) | |||||
{ | |||||
if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty()) | |||||
return new iOSAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName | |||||
: inputDeviceName); | |||||
return nullptr; | |||||
} | |||||
private: | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSAudioIODeviceType) | |||||
}; | |||||
//============================================================================== | |||||
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio() | |||||
{ | |||||
return new iOSAudioIODeviceType(); | |||||
} |
@@ -0,0 +1,78 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
AudioCDReader::AudioCDReader() | |||||
: AudioFormatReader (0, "CD Audio") | |||||
{ | |||||
} | |||||
StringArray AudioCDReader::getAvailableCDNames() | |||||
{ | |||||
StringArray names; | |||||
return names; | |||||
} | |||||
AudioCDReader* AudioCDReader::createReaderForCD (const int index) | |||||
{ | |||||
return nullptr; | |||||
} | |||||
AudioCDReader::~AudioCDReader() | |||||
{ | |||||
} | |||||
void AudioCDReader::refreshTrackLengths() | |||||
{ | |||||
} | |||||
bool AudioCDReader::readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | |||||
int64 startSampleInFile, int numSamples) | |||||
{ | |||||
return false; | |||||
} | |||||
bool AudioCDReader::isCDStillPresent() const | |||||
{ | |||||
return false; | |||||
} | |||||
bool AudioCDReader::isTrackAudio (int trackNum) const | |||||
{ | |||||
return false; | |||||
} | |||||
void AudioCDReader::enableIndexScanning (bool b) | |||||
{ | |||||
} | |||||
int AudioCDReader::getLastIndex() const | |||||
{ | |||||
return 0; | |||||
} | |||||
Array<int> AudioCDReader::findIndexesInTrack (const int trackNumber) | |||||
{ | |||||
return Array<int>(); | |||||
} |
@@ -0,0 +1,606 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
//============================================================================== | |||||
static void* juce_libjackHandle = nullptr; | |||||
static void* juce_loadJackFunction (const char* const name) | |||||
{ | |||||
if (juce_libjackHandle == nullptr) | |||||
return nullptr; | |||||
return dlsym (juce_libjackHandle, name); | |||||
} | |||||
#define JUCE_DECL_JACK_FUNCTION(return_type, fn_name, argument_types, arguments) \ | |||||
return_type fn_name argument_types \ | |||||
{ \ | |||||
typedef return_type (*fn_type) argument_types; \ | |||||
static fn_type fn = (fn_type) juce_loadJackFunction (#fn_name); \ | |||||
return (fn != nullptr) ? ((*fn) arguments) : (return_type) 0; \ | |||||
} | |||||
#define JUCE_DECL_VOID_JACK_FUNCTION(fn_name, argument_types, arguments) \ | |||||
void fn_name argument_types \ | |||||
{ \ | |||||
typedef void (*fn_type) argument_types; \ | |||||
static fn_type fn = (fn_type) juce_loadJackFunction (#fn_name); \ | |||||
if (fn != nullptr) (*fn) arguments; \ | |||||
} | |||||
//============================================================================== | |||||
JUCE_DECL_JACK_FUNCTION (jack_client_t*, jack_client_open, (const char* client_name, jack_options_t options, jack_status_t* status, ...), (client_name, options, status)); | |||||
JUCE_DECL_JACK_FUNCTION (int, jack_client_close, (jack_client_t *client), (client)); | |||||
JUCE_DECL_JACK_FUNCTION (int, jack_activate, (jack_client_t* client), (client)); | |||||
JUCE_DECL_JACK_FUNCTION (int, jack_deactivate, (jack_client_t* client), (client)); | |||||
JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_get_buffer_size, (jack_client_t* client), (client)); | |||||
JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_get_sample_rate, (jack_client_t* client), (client)); | |||||
JUCE_DECL_VOID_JACK_FUNCTION (jack_on_shutdown, (jack_client_t* client, void (*function)(void* arg), void* arg), (client, function, arg)); | |||||
JUCE_DECL_JACK_FUNCTION (void* , jack_port_get_buffer, (jack_port_t* port, jack_nframes_t nframes), (port, nframes)); | |||||
JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_port_get_total_latency, (jack_client_t* client, jack_port_t* port), (client, port)); | |||||
JUCE_DECL_JACK_FUNCTION (jack_port_t* , jack_port_register, (jack_client_t* client, const char* port_name, const char* port_type, unsigned long flags, unsigned long buffer_size), (client, port_name, port_type, flags, buffer_size)); | |||||
JUCE_DECL_VOID_JACK_FUNCTION (jack_set_error_function, (void (*func)(const char*)), (func)); | |||||
JUCE_DECL_JACK_FUNCTION (int, jack_set_process_callback, (jack_client_t* client, JackProcessCallback process_callback, void* arg), (client, process_callback, arg)); | |||||
JUCE_DECL_JACK_FUNCTION (const char**, jack_get_ports, (jack_client_t* client, const char* port_name_pattern, const char* type_name_pattern, unsigned long flags), (client, port_name_pattern, type_name_pattern, flags)); | |||||
JUCE_DECL_JACK_FUNCTION (int, jack_connect, (jack_client_t* client, const char* source_port, const char* destination_port), (client, source_port, destination_port)); | |||||
JUCE_DECL_JACK_FUNCTION (const char*, jack_port_name, (const jack_port_t* port), (port)); | |||||
JUCE_DECL_JACK_FUNCTION (void*, jack_set_port_connect_callback, (jack_client_t* client, JackPortConnectCallback connect_callback, void* arg), (client, connect_callback, arg)); | |||||
JUCE_DECL_JACK_FUNCTION (jack_port_t* , jack_port_by_id, (jack_client_t* client, jack_port_id_t port_id), (client, port_id)); | |||||
JUCE_DECL_JACK_FUNCTION (int, jack_port_connected, (const jack_port_t* port), (port)); | |||||
JUCE_DECL_JACK_FUNCTION (int, jack_port_connected_to, (const jack_port_t* port, const char* port_name), (port, port_name)); | |||||
#if JUCE_DEBUG | |||||
#define JACK_LOGGING_ENABLED 1 | |||||
#endif | |||||
#if JACK_LOGGING_ENABLED | |||||
namespace | |||||
{ | |||||
void jack_Log (const String& s) | |||||
{ | |||||
std::cerr << s << std::endl; | |||||
} | |||||
void dumpJackErrorMessage (const jack_status_t status) | |||||
{ | |||||
if (status & JackServerFailed || status & JackServerError) jack_Log ("Unable to connect to JACK server"); | |||||
if (status & JackVersionError) jack_Log ("Client's protocol version does not match"); | |||||
if (status & JackInvalidOption) jack_Log ("The operation contained an invalid or unsupported option"); | |||||
if (status & JackNameNotUnique) jack_Log ("The desired client name was not unique"); | |||||
if (status & JackNoSuchClient) jack_Log ("Requested client does not exist"); | |||||
if (status & JackInitFailure) jack_Log ("Unable to initialize client"); | |||||
} | |||||
} | |||||
#else | |||||
#define dumpJackErrorMessage(a) {} | |||||
#define jack_Log(...) {} | |||||
#endif | |||||
//============================================================================== | |||||
#ifndef JUCE_JACK_CLIENT_NAME | |||||
#define JUCE_JACK_CLIENT_NAME "JUCEJack" | |||||
#endif | |||||
static const char** getJackPorts (jack_client_t* const client, const bool forInput) | |||||
{ | |||||
if (client != nullptr) | |||||
return juce::jack_get_ports (client, nullptr, nullptr, | |||||
forInput ? JackPortIsOutput : JackPortIsInput); | |||||
// (NB: This looks like it's the wrong way round, but it is correct!) | |||||
return nullptr; | |||||
} | |||||
class JackAudioIODeviceType; | |||||
static Array<JackAudioIODeviceType*> activeDeviceTypes; | |||||
//============================================================================== | |||||
class JackAudioIODevice : public AudioIODevice | |||||
{ | |||||
public: | |||||
JackAudioIODevice (const String& deviceName, | |||||
const String& inId, | |||||
const String& outId) | |||||
: AudioIODevice (deviceName, "JACK"), | |||||
inputId (inId), | |||||
outputId (outId), | |||||
isOpen_ (false), | |||||
callback (nullptr), | |||||
totalNumberOfInputChannels (0), | |||||
totalNumberOfOutputChannels (0) | |||||
{ | |||||
jassert (deviceName.isNotEmpty()); | |||||
jack_status_t status; | |||||
client = juce::jack_client_open (JUCE_JACK_CLIENT_NAME, JackNoStartServer, &status); | |||||
if (client == nullptr) | |||||
{ | |||||
dumpJackErrorMessage (status); | |||||
} | |||||
else | |||||
{ | |||||
juce::jack_set_error_function (errorCallback); | |||||
// open input ports | |||||
const StringArray inputChannels (getInputChannelNames()); | |||||
for (int i = 0; i < inputChannels.size(); ++i) | |||||
{ | |||||
String inputName; | |||||
inputName << "in_" << ++totalNumberOfInputChannels; | |||||
inputPorts.add (juce::jack_port_register (client, inputName.toUTF8(), | |||||
JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0)); | |||||
} | |||||
// open output ports | |||||
const StringArray outputChannels (getOutputChannelNames()); | |||||
for (int i = 0; i < outputChannels.size (); ++i) | |||||
{ | |||||
String outputName; | |||||
outputName << "out_" << ++totalNumberOfOutputChannels; | |||||
outputPorts.add (juce::jack_port_register (client, outputName.toUTF8(), | |||||
JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0)); | |||||
} | |||||
inChans.calloc (totalNumberOfInputChannels + 2); | |||||
outChans.calloc (totalNumberOfOutputChannels + 2); | |||||
} | |||||
} | |||||
~JackAudioIODevice() | |||||
{ | |||||
close(); | |||||
if (client != nullptr) | |||||
{ | |||||
juce::jack_client_close (client); | |||||
client = nullptr; | |||||
} | |||||
} | |||||
StringArray getChannelNames (bool forInput) const | |||||
{ | |||||
StringArray names; | |||||
if (const char** const ports = getJackPorts (client, forInput)) | |||||
{ | |||||
for (int j = 0; ports[j] != nullptr; ++j) | |||||
{ | |||||
const String portName (ports [j]); | |||||
if (portName.upToFirstOccurrenceOf (":", false, false) == getName()) | |||||
names.add (portName.fromFirstOccurrenceOf (":", false, false)); | |||||
} | |||||
free (ports); | |||||
} | |||||
return names; | |||||
} | |||||
StringArray getOutputChannelNames() { return getChannelNames (false); } | |||||
StringArray getInputChannelNames() { return getChannelNames (true); } | |||||
int getNumSampleRates() { return client != nullptr ? 1 : 0; } | |||||
double getSampleRate (int /*index*/) { return client != nullptr ? juce::jack_get_sample_rate (client) : 0; } | |||||
int getNumBufferSizesAvailable() { return client != nullptr ? 1 : 0; } | |||||
int getBufferSizeSamples (int /*index*/) { return getDefaultBufferSize(); } | |||||
int getDefaultBufferSize() { return client != nullptr ? juce::jack_get_buffer_size (client) : 0; } | |||||
String open (const BigInteger& inputChannels, const BigInteger& outputChannels, | |||||
double /* sampleRate */, int /* bufferSizeSamples */) | |||||
{ | |||||
if (client == nullptr) | |||||
{ | |||||
lastError = "No JACK client running"; | |||||
return lastError; | |||||
} | |||||
lastError = String::empty; | |||||
close(); | |||||
juce::jack_set_process_callback (client, processCallback, this); | |||||
juce::jack_set_port_connect_callback (client, portConnectCallback, this); | |||||
juce::jack_on_shutdown (client, shutdownCallback, this); | |||||
juce::jack_activate (client); | |||||
isOpen_ = true; | |||||
if (! inputChannels.isZero()) | |||||
{ | |||||
if (const char** const ports = getJackPorts (client, true)) | |||||
{ | |||||
const int numInputChannels = inputChannels.getHighestBit() + 1; | |||||
for (int i = 0; i < numInputChannels; ++i) | |||||
{ | |||||
const String portName (ports[i]); | |||||
if (inputChannels[i] && portName.upToFirstOccurrenceOf (":", false, false) == getName()) | |||||
{ | |||||
int error = juce::jack_connect (client, ports[i], juce::jack_port_name ((jack_port_t*) inputPorts[i])); | |||||
if (error != 0) | |||||
jack_Log ("Cannot connect input port " + String (i) + " (" + String (ports[i]) + "), error " + String (error)); | |||||
} | |||||
} | |||||
free (ports); | |||||
} | |||||
} | |||||
if (! outputChannels.isZero()) | |||||
{ | |||||
if (const char** const ports = getJackPorts (client, false)) | |||||
{ | |||||
const int numOutputChannels = outputChannels.getHighestBit() + 1; | |||||
for (int i = 0; i < numOutputChannels; ++i) | |||||
{ | |||||
const String portName (ports[i]); | |||||
if (outputChannels[i] && portName.upToFirstOccurrenceOf (":", false, false) == getName()) | |||||
{ | |||||
int error = juce::jack_connect (client, juce::jack_port_name ((jack_port_t*) outputPorts[i]), ports[i]); | |||||
if (error != 0) | |||||
jack_Log ("Cannot connect output port " + String (i) + " (" + String (ports[i]) + "), error " + String (error)); | |||||
} | |||||
} | |||||
free (ports); | |||||
} | |||||
} | |||||
return lastError; | |||||
} | |||||
void close() | |||||
{ | |||||
stop(); | |||||
if (client != nullptr) | |||||
{ | |||||
juce::jack_deactivate (client); | |||||
juce::jack_set_process_callback (client, processCallback, nullptr); | |||||
juce::jack_set_port_connect_callback (client, portConnectCallback, nullptr); | |||||
juce::jack_on_shutdown (client, shutdownCallback, nullptr); | |||||
} | |||||
isOpen_ = false; | |||||
} | |||||
void start (AudioIODeviceCallback* newCallback) | |||||
{ | |||||
if (isOpen_ && newCallback != callback) | |||||
{ | |||||
if (newCallback != nullptr) | |||||
newCallback->audioDeviceAboutToStart (this); | |||||
AudioIODeviceCallback* const oldCallback = callback; | |||||
{ | |||||
const ScopedLock sl (callbackLock); | |||||
callback = newCallback; | |||||
} | |||||
if (oldCallback != nullptr) | |||||
oldCallback->audioDeviceStopped(); | |||||
} | |||||
} | |||||
void stop() | |||||
{ | |||||
start (nullptr); | |||||
} | |||||
bool isOpen() { return isOpen_; } | |||||
bool isPlaying() { return callback != nullptr; } | |||||
int getCurrentBufferSizeSamples() { return getBufferSizeSamples (0); } | |||||
double getCurrentSampleRate() { return getSampleRate (0); } | |||||
int getCurrentBitDepth() { return 32; } | |||||
String getLastError() { return lastError; } | |||||
BigInteger getActiveOutputChannels() const { return activeOutputChannels; } | |||||
BigInteger getActiveInputChannels() const { return activeInputChannels; } | |||||
int getOutputLatencyInSamples() | |||||
{ | |||||
int latency = 0; | |||||
for (int i = 0; i < outputPorts.size(); i++) | |||||
latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, (jack_port_t*) outputPorts [i])); | |||||
return latency; | |||||
} | |||||
int getInputLatencyInSamples() | |||||
{ | |||||
int latency = 0; | |||||
for (int i = 0; i < inputPorts.size(); i++) | |||||
latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, (jack_port_t*) inputPorts [i])); | |||||
return latency; | |||||
} | |||||
String inputId, outputId; | |||||
private: | |||||
void process (const int numSamples) | |||||
{ | |||||
int numActiveInChans = 0, numActiveOutChans = 0; | |||||
for (int i = 0; i < totalNumberOfInputChannels; ++i) | |||||
{ | |||||
if (activeInputChannels[i]) | |||||
if (jack_default_audio_sample_t* in | |||||
= (jack_default_audio_sample_t*) juce::jack_port_get_buffer ((jack_port_t*) inputPorts.getUnchecked(i), numSamples)) | |||||
inChans [numActiveInChans++] = (float*) in; | |||||
} | |||||
for (int i = 0; i < totalNumberOfOutputChannels; ++i) | |||||
{ | |||||
if (activeOutputChannels[i]) | |||||
if (jack_default_audio_sample_t* out | |||||
= (jack_default_audio_sample_t*) juce::jack_port_get_buffer ((jack_port_t*) outputPorts.getUnchecked(i), numSamples)) | |||||
outChans [numActiveOutChans++] = (float*) out; | |||||
} | |||||
const ScopedLock sl (callbackLock); | |||||
if (callback != nullptr) | |||||
{ | |||||
if ((numActiveInChans + numActiveOutChans) > 0) | |||||
callback->audioDeviceIOCallback (const_cast <const float**> (inChans.getData()), numActiveInChans, | |||||
outChans, numActiveOutChans, numSamples); | |||||
} | |||||
else | |||||
{ | |||||
for (int i = 0; i < numActiveOutChans; ++i) | |||||
zeromem (outChans[i], sizeof (float) * numSamples); | |||||
} | |||||
} | |||||
static int processCallback (jack_nframes_t nframes, void* callbackArgument) | |||||
{ | |||||
if (callbackArgument != nullptr) | |||||
((JackAudioIODevice*) callbackArgument)->process (nframes); | |||||
return 0; | |||||
} | |||||
void updateActivePorts() | |||||
{ | |||||
BigInteger newOutputChannels, newInputChannels; | |||||
for (int i = 0; i < outputPorts.size(); ++i) | |||||
if (juce::jack_port_connected ((jack_port_t*) outputPorts.getUnchecked(i))) | |||||
newOutputChannels.setBit (i); | |||||
for (int i = 0; i < inputPorts.size(); ++i) | |||||
if (juce::jack_port_connected ((jack_port_t*) inputPorts.getUnchecked(i))) | |||||
newInputChannels.setBit (i); | |||||
if (newOutputChannels != activeOutputChannels | |||||
|| newInputChannels != activeInputChannels) | |||||
{ | |||||
AudioIODeviceCallback* const oldCallback = callback; | |||||
stop(); | |||||
activeOutputChannels = newOutputChannels; | |||||
activeInputChannels = newInputChannels; | |||||
if (oldCallback != nullptr) | |||||
start (oldCallback); | |||||
sendDeviceChangedCallback(); | |||||
} | |||||
} | |||||
static void portConnectCallback (jack_port_id_t, jack_port_id_t, int, void* arg) | |||||
{ | |||||
if (JackAudioIODevice* device = static_cast <JackAudioIODevice*> (arg)) | |||||
device->updateActivePorts(); | |||||
} | |||||
static void threadInitCallback (void* /* callbackArgument */) | |||||
{ | |||||
jack_Log ("JackAudioIODevice::initialise"); | |||||
} | |||||
static void shutdownCallback (void* callbackArgument) | |||||
{ | |||||
jack_Log ("JackAudioIODevice::shutdown"); | |||||
if (JackAudioIODevice* device = (JackAudioIODevice*) callbackArgument) | |||||
{ | |||||
device->client = nullptr; | |||||
device->close(); | |||||
} | |||||
} | |||||
static void errorCallback (const char* msg) | |||||
{ | |||||
jack_Log ("JackAudioIODevice::errorCallback " + String (msg)); | |||||
} | |||||
static void sendDeviceChangedCallback(); | |||||
bool isOpen_; | |||||
jack_client_t* client; | |||||
String lastError; | |||||
AudioIODeviceCallback* callback; | |||||
CriticalSection callbackLock; | |||||
HeapBlock <float*> inChans, outChans; | |||||
int totalNumberOfInputChannels; | |||||
int totalNumberOfOutputChannels; | |||||
Array<void*> inputPorts, outputPorts; | |||||
BigInteger activeInputChannels, activeOutputChannels; | |||||
}; | |||||
//============================================================================== | |||||
class JackAudioIODeviceType : public AudioIODeviceType | |||||
{ | |||||
public: | |||||
JackAudioIODeviceType() | |||||
: AudioIODeviceType ("JACK"), | |||||
hasScanned (false) | |||||
{ | |||||
activeDeviceTypes.add (this); | |||||
} | |||||
~JackAudioIODeviceType() | |||||
{ | |||||
activeDeviceTypes.removeFirstMatchingValue (this); | |||||
} | |||||
void scanForDevices() | |||||
{ | |||||
hasScanned = true; | |||||
inputNames.clear(); | |||||
inputIds.clear(); | |||||
outputNames.clear(); | |||||
outputIds.clear(); | |||||
if (juce_libjackHandle == nullptr) | |||||
{ | |||||
juce_libjackHandle = dlopen ("libjack.so", RTLD_LAZY); | |||||
if (juce_libjackHandle == nullptr) | |||||
return; | |||||
} | |||||
jack_status_t status; | |||||
// open a dummy client | |||||
if (jack_client_t* const client = juce::jack_client_open ("JuceJackDummy", JackNoStartServer, &status)) | |||||
{ | |||||
// scan for output devices | |||||
if (const char** const ports = getJackPorts (client, false)) | |||||
{ | |||||
for (int j = 0; ports[j] != nullptr; ++j) | |||||
{ | |||||
String clientName (ports[j]); | |||||
clientName = clientName.upToFirstOccurrenceOf (":", false, false); | |||||
if (clientName != (JUCE_JACK_CLIENT_NAME) && ! inputNames.contains (clientName)) | |||||
{ | |||||
inputNames.add (clientName); | |||||
inputIds.add (ports [j]); | |||||
} | |||||
} | |||||
free (ports); | |||||
} | |||||
// scan for input devices | |||||
if (const char** const ports = getJackPorts (client, true)) | |||||
{ | |||||
for (int j = 0; ports[j] != nullptr; ++j) | |||||
{ | |||||
String clientName (ports[j]); | |||||
clientName = clientName.upToFirstOccurrenceOf (":", false, false); | |||||
if (clientName != (JUCE_JACK_CLIENT_NAME) && ! outputNames.contains (clientName)) | |||||
{ | |||||
outputNames.add (clientName); | |||||
outputIds.add (ports [j]); | |||||
} | |||||
} | |||||
free (ports); | |||||
} | |||||
juce::jack_client_close (client); | |||||
} | |||||
else | |||||
{ | |||||
dumpJackErrorMessage (status); | |||||
} | |||||
} | |||||
StringArray getDeviceNames (bool wantInputNames) const | |||||
{ | |||||
jassert (hasScanned); // need to call scanForDevices() before doing this | |||||
return wantInputNames ? inputNames : outputNames; | |||||
} | |||||
int getDefaultDeviceIndex (bool /* forInput */) const | |||||
{ | |||||
jassert (hasScanned); // need to call scanForDevices() before doing this | |||||
return 0; | |||||
} | |||||
bool hasSeparateInputsAndOutputs() const { return true; } | |||||
int getIndexOfDevice (AudioIODevice* device, bool asInput) const | |||||
{ | |||||
jassert (hasScanned); // need to call scanForDevices() before doing this | |||||
if (JackAudioIODevice* d = dynamic_cast <JackAudioIODevice*> (device)) | |||||
return asInput ? inputIds.indexOf (d->inputId) | |||||
: outputIds.indexOf (d->outputId); | |||||
return -1; | |||||
} | |||||
AudioIODevice* createDevice (const String& outputDeviceName, | |||||
const String& inputDeviceName) | |||||
{ | |||||
jassert (hasScanned); // need to call scanForDevices() before doing this | |||||
const int inputIndex = inputNames.indexOf (inputDeviceName); | |||||
const int outputIndex = outputNames.indexOf (outputDeviceName); | |||||
if (inputIndex >= 0 || outputIndex >= 0) | |||||
return new JackAudioIODevice (outputIndex >= 0 ? outputDeviceName | |||||
: inputDeviceName, | |||||
inputIds [inputIndex], | |||||
outputIds [outputIndex]); | |||||
return nullptr; | |||||
} | |||||
void portConnectionChange() { callDeviceChangeListeners(); } | |||||
private: | |||||
StringArray inputNames, outputNames, inputIds, outputIds; | |||||
bool hasScanned; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JackAudioIODeviceType) | |||||
}; | |||||
void JackAudioIODevice::sendDeviceChangedCallback() | |||||
{ | |||||
for (int i = activeDeviceTypes.size(); --i >= 0;) | |||||
if (JackAudioIODeviceType* d = activeDeviceTypes[i]) | |||||
d->portConnectionChange(); | |||||
} | |||||
//============================================================================== | |||||
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK() | |||||
{ | |||||
return new JackAudioIODeviceType(); | |||||
} |
@@ -0,0 +1,476 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#if JUCE_ALSA | |||||
// You can define these strings in your app if you want to override the default names: | |||||
#ifndef JUCE_ALSA_MIDI_INPUT_NAME | |||||
#define JUCE_ALSA_MIDI_INPUT_NAME "Juce Midi Input" | |||||
#endif | |||||
#ifndef JUCE_ALSA_MIDI_OUTPUT_NAME | |||||
#define JUCE_ALSA_MIDI_OUTPUT_NAME "Juce Midi Output" | |||||
#endif | |||||
#ifndef JUCE_ALSA_MIDI_INPUT_PORT_NAME | |||||
#define JUCE_ALSA_MIDI_INPUT_PORT_NAME "Juce Midi In Port" | |||||
#endif | |||||
#ifndef JUCE_ALSA_MIDI_OUTPUT_PORT_NAME | |||||
#define JUCE_ALSA_MIDI_OUTPUT_PORT_NAME "Juce Midi Out Port" | |||||
#endif | |||||
//============================================================================== | |||||
namespace | |||||
{ | |||||
snd_seq_t* iterateMidiClient (snd_seq_t* seqHandle, | |||||
snd_seq_client_info_t* clientInfo, | |||||
const bool forInput, | |||||
StringArray& deviceNamesFound, | |||||
const int deviceIndexToOpen) | |||||
{ | |||||
snd_seq_t* returnedHandle = nullptr; | |||||
snd_seq_port_info_t* portInfo; | |||||
if (snd_seq_port_info_malloc (&portInfo) == 0) | |||||
{ | |||||
int numPorts = snd_seq_client_info_get_num_ports (clientInfo); | |||||
const int client = snd_seq_client_info_get_client (clientInfo); | |||||
snd_seq_port_info_set_client (portInfo, client); | |||||
snd_seq_port_info_set_port (portInfo, -1); | |||||
while (--numPorts >= 0) | |||||
{ | |||||
if (snd_seq_query_next_port (seqHandle, portInfo) == 0 | |||||
&& (snd_seq_port_info_get_capability (portInfo) | |||||
& (forInput ? SND_SEQ_PORT_CAP_READ | |||||
: SND_SEQ_PORT_CAP_WRITE)) != 0) | |||||
{ | |||||
deviceNamesFound.add (snd_seq_client_info_get_name (clientInfo)); | |||||
if (deviceNamesFound.size() == deviceIndexToOpen + 1) | |||||
{ | |||||
const int sourcePort = snd_seq_port_info_get_port (portInfo); | |||||
const int sourceClient = snd_seq_client_info_get_client (clientInfo); | |||||
if (sourcePort != -1) | |||||
{ | |||||
if (forInput) | |||||
{ | |||||
snd_seq_set_client_name (seqHandle, JUCE_ALSA_MIDI_INPUT_NAME); | |||||
const int portId = snd_seq_create_simple_port (seqHandle, JUCE_ALSA_MIDI_INPUT_PORT_NAME, | |||||
SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE, | |||||
SND_SEQ_PORT_TYPE_MIDI_GENERIC); | |||||
snd_seq_connect_from (seqHandle, portId, sourceClient, sourcePort); | |||||
} | |||||
else | |||||
{ | |||||
snd_seq_set_client_name (seqHandle, JUCE_ALSA_MIDI_OUTPUT_NAME); | |||||
const int portId = snd_seq_create_simple_port (seqHandle, JUCE_ALSA_MIDI_OUTPUT_PORT_NAME, | |||||
SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, | |||||
SND_SEQ_PORT_TYPE_MIDI_GENERIC); | |||||
snd_seq_connect_to (seqHandle, portId, sourceClient, sourcePort); | |||||
} | |||||
returnedHandle = seqHandle; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
snd_seq_port_info_free (portInfo); | |||||
} | |||||
return returnedHandle; | |||||
} | |||||
snd_seq_t* iterateMidiDevices (const bool forInput, | |||||
StringArray& deviceNamesFound, | |||||
const int deviceIndexToOpen) | |||||
{ | |||||
snd_seq_t* returnedHandle = nullptr; | |||||
snd_seq_t* seqHandle = nullptr; | |||||
if (snd_seq_open (&seqHandle, "default", forInput ? SND_SEQ_OPEN_INPUT | |||||
: SND_SEQ_OPEN_OUTPUT, 0) == 0) | |||||
{ | |||||
snd_seq_system_info_t* systemInfo = nullptr; | |||||
snd_seq_client_info_t* clientInfo = nullptr; | |||||
if (snd_seq_system_info_malloc (&systemInfo) == 0) | |||||
{ | |||||
if (snd_seq_system_info (seqHandle, systemInfo) == 0 | |||||
&& snd_seq_client_info_malloc (&clientInfo) == 0) | |||||
{ | |||||
int numClients = snd_seq_system_info_get_cur_clients (systemInfo); | |||||
while (--numClients >= 0 && returnedHandle == 0) | |||||
if (snd_seq_query_next_client (seqHandle, clientInfo) == 0) | |||||
returnedHandle = iterateMidiClient (seqHandle, clientInfo, | |||||
forInput, deviceNamesFound, | |||||
deviceIndexToOpen); | |||||
snd_seq_client_info_free (clientInfo); | |||||
} | |||||
snd_seq_system_info_free (systemInfo); | |||||
} | |||||
if (returnedHandle == 0) | |||||
snd_seq_close (seqHandle); | |||||
} | |||||
deviceNamesFound.appendNumbersToDuplicates (true, true); | |||||
return returnedHandle; | |||||
} | |||||
snd_seq_t* createMidiDevice (const bool forInput, const String& deviceNameToOpen) | |||||
{ | |||||
snd_seq_t* seqHandle = nullptr; | |||||
if (snd_seq_open (&seqHandle, "default", forInput ? SND_SEQ_OPEN_INPUT | |||||
: SND_SEQ_OPEN_OUTPUT, 0) == 0) | |||||
{ | |||||
snd_seq_set_client_name (seqHandle, | |||||
(deviceNameToOpen + (forInput ? " Input" : " Output")).toUTF8()); | |||||
const int portId | |||||
= snd_seq_create_simple_port (seqHandle, | |||||
forInput ? "in" | |||||
: "out", | |||||
forInput ? (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE) | |||||
: (SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ), | |||||
forInput ? SND_SEQ_PORT_TYPE_APPLICATION | |||||
: SND_SEQ_PORT_TYPE_MIDI_GENERIC); | |||||
if (portId < 0) | |||||
{ | |||||
snd_seq_close (seqHandle); | |||||
seqHandle = nullptr; | |||||
} | |||||
} | |||||
return seqHandle; | |||||
} | |||||
} | |||||
//============================================================================== | |||||
class MidiOutputDevice | |||||
{ | |||||
public: | |||||
MidiOutputDevice (MidiOutput* const midiOutput_, | |||||
snd_seq_t* const seqHandle_) | |||||
: | |||||
midiOutput (midiOutput_), | |||||
seqHandle (seqHandle_), | |||||
maxEventSize (16 * 1024) | |||||
{ | |||||
jassert (seqHandle != 0 && midiOutput != 0); | |||||
snd_midi_event_new (maxEventSize, &midiParser); | |||||
} | |||||
~MidiOutputDevice() | |||||
{ | |||||
snd_midi_event_free (midiParser); | |||||
snd_seq_close (seqHandle); | |||||
} | |||||
void sendMessageNow (const MidiMessage& message) | |||||
{ | |||||
if (message.getRawDataSize() > maxEventSize) | |||||
{ | |||||
maxEventSize = message.getRawDataSize(); | |||||
snd_midi_event_free (midiParser); | |||||
snd_midi_event_new (maxEventSize, &midiParser); | |||||
} | |||||
snd_seq_event_t event; | |||||
snd_seq_ev_clear (&event); | |||||
long numBytes = (long) message.getRawDataSize(); | |||||
const uint8* data = message.getRawData(); | |||||
while (numBytes > 0) | |||||
{ | |||||
const long numSent = snd_midi_event_encode (midiParser, data, numBytes, &event); | |||||
if (numSent <= 0) | |||||
break; | |||||
numBytes -= numSent; | |||||
data += numSent; | |||||
snd_seq_ev_set_source (&event, 0); | |||||
snd_seq_ev_set_subs (&event); | |||||
snd_seq_ev_set_direct (&event); | |||||
snd_seq_event_output (seqHandle, &event); | |||||
} | |||||
snd_seq_drain_output (seqHandle); | |||||
snd_midi_event_reset_encode (midiParser); | |||||
} | |||||
private: | |||||
MidiOutput* const midiOutput; | |||||
snd_seq_t* const seqHandle; | |||||
snd_midi_event_t* midiParser; | |||||
int maxEventSize; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutputDevice) | |||||
}; | |||||
StringArray MidiOutput::getDevices() | |||||
{ | |||||
StringArray devices; | |||||
iterateMidiDevices (false, devices, -1); | |||||
return devices; | |||||
} | |||||
int MidiOutput::getDefaultDeviceIndex() | |||||
{ | |||||
return 0; | |||||
} | |||||
MidiOutput* MidiOutput::openDevice (int deviceIndex) | |||||
{ | |||||
MidiOutput* newDevice = nullptr; | |||||
StringArray devices; | |||||
snd_seq_t* const handle = iterateMidiDevices (false, devices, deviceIndex); | |||||
if (handle != 0) | |||||
{ | |||||
newDevice = new MidiOutput(); | |||||
newDevice->internal = new MidiOutputDevice (newDevice, handle); | |||||
} | |||||
return newDevice; | |||||
} | |||||
MidiOutput* MidiOutput::createNewDevice (const String& deviceName) | |||||
{ | |||||
MidiOutput* newDevice = nullptr; | |||||
snd_seq_t* const handle = createMidiDevice (false, deviceName); | |||||
if (handle != 0) | |||||
{ | |||||
newDevice = new MidiOutput(); | |||||
newDevice->internal = new MidiOutputDevice (newDevice, handle); | |||||
} | |||||
return newDevice; | |||||
} | |||||
MidiOutput::~MidiOutput() | |||||
{ | |||||
delete static_cast <MidiOutputDevice*> (internal); | |||||
} | |||||
void MidiOutput::sendMessageNow (const MidiMessage& message) | |||||
{ | |||||
static_cast <MidiOutputDevice*> (internal)->sendMessageNow (message); | |||||
} | |||||
//============================================================================== | |||||
class MidiInputThread : public Thread | |||||
{ | |||||
public: | |||||
MidiInputThread (MidiInput* const midiInput_, | |||||
snd_seq_t* const seqHandle_, | |||||
MidiInputCallback* const callback_) | |||||
: Thread ("Juce MIDI Input"), | |||||
midiInput (midiInput_), | |||||
seqHandle (seqHandle_), | |||||
callback (callback_) | |||||
{ | |||||
jassert (seqHandle != 0 && callback != 0 && midiInput != 0); | |||||
} | |||||
~MidiInputThread() | |||||
{ | |||||
snd_seq_close (seqHandle); | |||||
} | |||||
void run() | |||||
{ | |||||
const int maxEventSize = 16 * 1024; | |||||
snd_midi_event_t* midiParser; | |||||
if (snd_midi_event_new (maxEventSize, &midiParser) >= 0) | |||||
{ | |||||
HeapBlock <uint8> buffer (maxEventSize); | |||||
const int numPfds = snd_seq_poll_descriptors_count (seqHandle, POLLIN); | |||||
struct pollfd* const pfd = (struct pollfd*) alloca (numPfds * sizeof (struct pollfd)); | |||||
snd_seq_poll_descriptors (seqHandle, pfd, numPfds, POLLIN); | |||||
while (! threadShouldExit()) | |||||
{ | |||||
if (poll (pfd, numPfds, 500) > 0) | |||||
{ | |||||
snd_seq_event_t* inputEvent = nullptr; | |||||
snd_seq_nonblock (seqHandle, 1); | |||||
do | |||||
{ | |||||
if (snd_seq_event_input (seqHandle, &inputEvent) >= 0) | |||||
{ | |||||
// xxx what about SYSEXes that are too big for the buffer? | |||||
const int numBytes = snd_midi_event_decode (midiParser, buffer, maxEventSize, inputEvent); | |||||
snd_midi_event_reset_decode (midiParser); | |||||
if (numBytes > 0) | |||||
{ | |||||
const MidiMessage message ((const uint8*) buffer, | |||||
numBytes, | |||||
Time::getMillisecondCounter() * 0.001); | |||||
callback->handleIncomingMidiMessage (midiInput, message); | |||||
} | |||||
snd_seq_free_event (inputEvent); | |||||
} | |||||
} | |||||
while (snd_seq_event_input_pending (seqHandle, 0) > 0); | |||||
snd_seq_free_event (inputEvent); | |||||
} | |||||
} | |||||
snd_midi_event_free (midiParser); | |||||
} | |||||
}; | |||||
private: | |||||
MidiInput* const midiInput; | |||||
snd_seq_t* const seqHandle; | |||||
MidiInputCallback* const callback; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInputThread) | |||||
}; | |||||
//============================================================================== | |||||
MidiInput::MidiInput (const String& name_) | |||||
: name (name_), | |||||
internal (0) | |||||
{ | |||||
} | |||||
MidiInput::~MidiInput() | |||||
{ | |||||
stop(); | |||||
delete static_cast <MidiInputThread*> (internal); | |||||
} | |||||
void MidiInput::start() | |||||
{ | |||||
static_cast <MidiInputThread*> (internal)->startThread(); | |||||
} | |||||
void MidiInput::stop() | |||||
{ | |||||
static_cast <MidiInputThread*> (internal)->stopThread (3000); | |||||
} | |||||
int MidiInput::getDefaultDeviceIndex() | |||||
{ | |||||
return 0; | |||||
} | |||||
StringArray MidiInput::getDevices() | |||||
{ | |||||
StringArray devices; | |||||
iterateMidiDevices (true, devices, -1); | |||||
return devices; | |||||
} | |||||
MidiInput* MidiInput::openDevice (int deviceIndex, MidiInputCallback* callback) | |||||
{ | |||||
MidiInput* newDevice = nullptr; | |||||
StringArray devices; | |||||
snd_seq_t* const handle = iterateMidiDevices (true, devices, deviceIndex); | |||||
if (handle != 0) | |||||
{ | |||||
newDevice = new MidiInput (devices [deviceIndex]); | |||||
newDevice->internal = new MidiInputThread (newDevice, handle, callback); | |||||
} | |||||
return newDevice; | |||||
} | |||||
MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) | |||||
{ | |||||
MidiInput* newDevice = nullptr; | |||||
snd_seq_t* const handle = createMidiDevice (true, deviceName); | |||||
if (handle != 0) | |||||
{ | |||||
newDevice = new MidiInput (deviceName); | |||||
newDevice->internal = new MidiInputThread (newDevice, handle, callback); | |||||
} | |||||
return newDevice; | |||||
} | |||||
//============================================================================== | |||||
#else | |||||
// (These are just stub functions if ALSA is unavailable...) | |||||
StringArray MidiOutput::getDevices() { return StringArray(); } | |||||
int MidiOutput::getDefaultDeviceIndex() { return 0; } | |||||
MidiOutput* MidiOutput::openDevice (int) { return nullptr; } | |||||
MidiOutput* MidiOutput::createNewDevice (const String&) { return nullptr; } | |||||
MidiOutput::~MidiOutput() {} | |||||
void MidiOutput::sendMessageNow (const MidiMessage&) {} | |||||
MidiInput::MidiInput (const String& name_) : name (name_), internal (0) {} | |||||
MidiInput::~MidiInput() {} | |||||
void MidiInput::start() {} | |||||
void MidiInput::stop() {} | |||||
int MidiInput::getDefaultDeviceIndex() { return 0; } | |||||
StringArray MidiInput::getDevices() { return StringArray(); } | |||||
MidiInput* MidiInput::openDevice (int, MidiInputCallback*) { return nullptr; } | |||||
MidiInput* MidiInput::createNewDevice (const String&, MidiInputCallback*) { return nullptr; } | |||||
#endif |
@@ -0,0 +1,492 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
const int kilobytesPerSecond1x = 176; | |||||
struct AudioTrackProducerClass : public ObjCClass <NSObject> | |||||
{ | |||||
AudioTrackProducerClass() : ObjCClass <NSObject> ("JUCEAudioTrackProducer_") | |||||
{ | |||||
addIvar<AudioSourceHolder*> ("source"); | |||||
addMethod (@selector (initWithAudioSourceHolder:), initWithAudioSourceHolder, "@@:^v"); | |||||
addMethod (@selector (cleanupTrackAfterBurn:), cleanupTrackAfterBurn, "v@:@"); | |||||
addMethod (@selector (cleanupTrackAfterVerification:), cleanupTrackAfterVerification, "c@:@"); | |||||
addMethod (@selector (estimateLengthOfTrack:), estimateLengthOfTrack, "Q@:@"); | |||||
addMethod (@selector (prepareTrack:forBurn:toMedia:), prepareTrack, "c@:@@@"); | |||||
addMethod (@selector (prepareTrackForVerification:), prepareTrackForVerification, "c@:@"); | |||||
addMethod (@selector (produceDataForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:), | |||||
produceDataForTrack, "I@:@^cIQI^I"); | |||||
addMethod (@selector (producePreGapForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:), | |||||
produceDataForTrack, "I@:@^cIQI^I"); | |||||
addMethod (@selector (verifyDataForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:), | |||||
produceDataForTrack, "I@:@^cIQI^I"); | |||||
registerClass(); | |||||
} | |||||
struct AudioSourceHolder | |||||
{ | |||||
AudioSourceHolder (AudioSource* source_, int numFrames) | |||||
: source (source_), readPosition (0), lengthInFrames (numFrames) | |||||
{ | |||||
} | |||||
~AudioSourceHolder() | |||||
{ | |||||
if (source != nullptr) | |||||
source->releaseResources(); | |||||
} | |||||
ScopedPointer<AudioSource> source; | |||||
int readPosition, lengthInFrames; | |||||
}; | |||||
private: | |||||
static id initWithAudioSourceHolder (id self, SEL, AudioSourceHolder* source) | |||||
{ | |||||
self = sendSuperclassMessage (self, @selector (init)); | |||||
object_setInstanceVariable (self, "source", source); | |||||
return self; | |||||
} | |||||
static AudioSourceHolder* getSource (id self) | |||||
{ | |||||
return getIvar<AudioSourceHolder*> (self, "source"); | |||||
} | |||||
static void dealloc (id self, SEL) | |||||
{ | |||||
delete getSource (self); | |||||
sendSuperclassMessage (self, @selector (dealloc)); | |||||
} | |||||
static void cleanupTrackAfterBurn (id self, SEL, DRTrack*) {} | |||||
static BOOL cleanupTrackAfterVerification (id self, SEL, DRTrack*) { return true; } | |||||
static uint64_t estimateLengthOfTrack (id self, SEL, DRTrack*) | |||||
{ | |||||
return getSource (self)->lengthInFrames; | |||||
} | |||||
static BOOL prepareTrack (id self, SEL, DRTrack*, DRBurn*, NSDictionary*) | |||||
{ | |||||
if (AudioSourceHolder* const source = getSource (self)) | |||||
{ | |||||
source->source->prepareToPlay (44100 / 75, 44100); | |||||
source->readPosition = 0; | |||||
} | |||||
return true; | |||||
} | |||||
static BOOL prepareTrackForVerification (id self, SEL, DRTrack*) | |||||
{ | |||||
if (AudioSourceHolder* const source = getSource (self)) | |||||
source->source->prepareToPlay (44100 / 75, 44100); | |||||
return true; | |||||
} | |||||
static uint32_t produceDataForTrack (id self, SEL, DRTrack*, char* buffer, | |||||
uint32_t bufferLength, uint64_t /*address*/, | |||||
uint32_t /*blockSize*/, uint32_t* /*flags*/) | |||||
{ | |||||
if (AudioSourceHolder* const source = getSource (self)) | |||||
{ | |||||
const int numSamples = jmin ((int) bufferLength / 4, | |||||
(source->lengthInFrames * (44100 / 75)) - source->readPosition); | |||||
if (numSamples > 0) | |||||
{ | |||||
AudioSampleBuffer tempBuffer (2, numSamples); | |||||
AudioSourceChannelInfo info (tempBuffer); | |||||
source->source->getNextAudioBlock (info); | |||||
typedef AudioData::Pointer <AudioData::Int16, | |||||
AudioData::LittleEndian, | |||||
AudioData::Interleaved, | |||||
AudioData::NonConst> CDSampleFormat; | |||||
typedef AudioData::Pointer <AudioData::Float32, | |||||
AudioData::NativeEndian, | |||||
AudioData::NonInterleaved, | |||||
AudioData::Const> SourceSampleFormat; | |||||
CDSampleFormat left (buffer, 2); | |||||
left.convertSamples (SourceSampleFormat (tempBuffer.getSampleData (0)), numSamples); | |||||
CDSampleFormat right (buffer + 2, 2); | |||||
right.convertSamples (SourceSampleFormat (tempBuffer.getSampleData (1)), numSamples); | |||||
source->readPosition += numSamples; | |||||
} | |||||
return numSamples * 4; | |||||
} | |||||
return 0; | |||||
} | |||||
static uint32_t producePreGapForTrack (id self, SEL, DRTrack*, char* buffer, | |||||
uint32_t bufferLength, uint64_t /*address*/, | |||||
uint32_t /*blockSize*/, uint32_t* /*flags*/) | |||||
{ | |||||
zeromem (buffer, bufferLength); | |||||
return bufferLength; | |||||
} | |||||
static BOOL verifyDataForTrack (id self, SEL, DRTrack*, const char*, | |||||
uint32_t /*bufferLength*/, uint64_t /*address*/, | |||||
uint32_t /*blockSize*/, uint32_t* /*flags*/) | |||||
{ | |||||
return true; | |||||
} | |||||
}; | |||||
struct OpenDiskDevice | |||||
{ | |||||
OpenDiskDevice (DRDevice* device_) | |||||
: device (device_), | |||||
tracks ([[NSMutableArray alloc] init]), | |||||
underrunProtection (true) | |||||
{ | |||||
} | |||||
~OpenDiskDevice() | |||||
{ | |||||
[tracks release]; | |||||
} | |||||
void addSourceTrack (AudioSource* source, int numSamples) | |||||
{ | |||||
if (source != nullptr) | |||||
{ | |||||
const int numFrames = (numSamples + 587) / 588; | |||||
static AudioTrackProducerClass cls; | |||||
NSObject* producer = [cls.createInstance() performSelector: @selector (initWithAudioSourceHolder:) | |||||
withObject: (id) new AudioTrackProducerClass::AudioSourceHolder (source, numFrames)]; | |||||
DRTrack* track = [[DRTrack alloc] initWithProducer: producer]; | |||||
{ | |||||
NSMutableDictionary* p = [[track properties] mutableCopy]; | |||||
[p setObject: [DRMSF msfWithFrames: numFrames] forKey: DRTrackLengthKey]; | |||||
[p setObject: [NSNumber numberWithUnsignedShort: 2352] forKey: DRBlockSizeKey]; | |||||
[p setObject: [NSNumber numberWithInt: 0] forKey: DRDataFormKey]; | |||||
[p setObject: [NSNumber numberWithInt: 0] forKey: DRBlockTypeKey]; | |||||
[p setObject: [NSNumber numberWithInt: 0] forKey: DRTrackModeKey]; | |||||
[p setObject: [NSNumber numberWithInt: 0] forKey: DRSessionFormatKey]; | |||||
[track setProperties: p]; | |||||
[p release]; | |||||
} | |||||
[tracks addObject: track]; | |||||
[track release]; | |||||
[producer release]; | |||||
} | |||||
} | |||||
String burn (AudioCDBurner::BurnProgressListener* listener, | |||||
bool shouldEject, bool peformFakeBurnForTesting, int burnSpeed) | |||||
{ | |||||
DRBurn* burn = [DRBurn burnForDevice: device]; | |||||
if (! [device acquireExclusiveAccess]) | |||||
return "Couldn't open or write to the CD device"; | |||||
[device acquireMediaReservation]; | |||||
NSMutableDictionary* d = [[burn properties] mutableCopy]; | |||||
[d autorelease]; | |||||
[d setObject: [NSNumber numberWithBool: peformFakeBurnForTesting] forKey: DRBurnTestingKey]; | |||||
[d setObject: [NSNumber numberWithBool: false] forKey: DRBurnVerifyDiscKey]; | |||||
[d setObject: (shouldEject ? DRBurnCompletionActionEject : DRBurnCompletionActionMount) forKey: DRBurnCompletionActionKey]; | |||||
if (burnSpeed > 0) | |||||
[d setObject: [NSNumber numberWithFloat: burnSpeed * kilobytesPerSecond1x] forKey: DRBurnRequestedSpeedKey]; | |||||
if (! underrunProtection) | |||||
[d setObject: [NSNumber numberWithBool: false] forKey: DRBurnUnderrunProtectionKey]; | |||||
[burn setProperties: d]; | |||||
[burn writeLayout: tracks]; | |||||
for (;;) | |||||
{ | |||||
Thread::sleep (300); | |||||
float progress = [[[burn status] objectForKey: DRStatusPercentCompleteKey] floatValue]; | |||||
if (listener != nullptr && listener->audioCDBurnProgress (progress)) | |||||
{ | |||||
[burn abort]; | |||||
return "User cancelled the write operation"; | |||||
} | |||||
if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateFailed]) | |||||
return "Write operation failed"; | |||||
if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateDone]) | |||||
break; | |||||
NSString* err = (NSString*) [[[burn status] objectForKey: DRErrorStatusKey] | |||||
objectForKey: DRErrorStatusErrorStringKey]; | |||||
if ([err length] > 0) | |||||
return CharPointer_UTF8 ([err UTF8String]); | |||||
} | |||||
[device releaseMediaReservation]; | |||||
[device releaseExclusiveAccess]; | |||||
return String::empty; | |||||
} | |||||
DRDevice* device; | |||||
NSMutableArray* tracks; | |||||
bool underrunProtection; | |||||
}; | |||||
//============================================================================== | |||||
class AudioCDBurner::Pimpl : public Timer | |||||
{ | |||||
public: | |||||
Pimpl (AudioCDBurner& owner_, const int deviceIndex) | |||||
: device (0), owner (owner_) | |||||
{ | |||||
DRDevice* dev = [[DRDevice devices] objectAtIndex: deviceIndex]; | |||||
if (dev != nil) | |||||
{ | |||||
device = new OpenDiskDevice (dev); | |||||
lastState = getDiskState(); | |||||
startTimer (1000); | |||||
} | |||||
} | |||||
~Pimpl() | |||||
{ | |||||
stopTimer(); | |||||
} | |||||
void timerCallback() | |||||
{ | |||||
const DiskState state = getDiskState(); | |||||
if (state != lastState) | |||||
{ | |||||
lastState = state; | |||||
owner.sendChangeMessage(); | |||||
} | |||||
} | |||||
DiskState getDiskState() const | |||||
{ | |||||
if ([device->device isValid]) | |||||
{ | |||||
NSDictionary* status = [device->device status]; | |||||
NSString* state = [status objectForKey: DRDeviceMediaStateKey]; | |||||
if ([state isEqualTo: DRDeviceMediaStateNone]) | |||||
{ | |||||
if ([[status objectForKey: DRDeviceIsTrayOpenKey] boolValue]) | |||||
return trayOpen; | |||||
return noDisc; | |||||
} | |||||
if ([state isEqualTo: DRDeviceMediaStateMediaPresent]) | |||||
{ | |||||
if ([[[status objectForKey: DRDeviceMediaInfoKey] objectForKey: DRDeviceMediaBlocksFreeKey] intValue] > 0) | |||||
return writableDiskPresent; | |||||
else | |||||
return readOnlyDiskPresent; | |||||
} | |||||
} | |||||
return unknown; | |||||
} | |||||
bool openTray() { return [device->device isValid] && [device->device ejectMedia]; } | |||||
Array<int> getAvailableWriteSpeeds() const | |||||
{ | |||||
Array<int> results; | |||||
if ([device->device isValid]) | |||||
{ | |||||
NSArray* speeds = [[[device->device status] objectForKey: DRDeviceMediaInfoKey] objectForKey: DRDeviceBurnSpeedsKey]; | |||||
for (unsigned int i = 0; i < [speeds count]; ++i) | |||||
{ | |||||
const int kbPerSec = [[speeds objectAtIndex: i] intValue]; | |||||
results.add (kbPerSec / kilobytesPerSecond1x); | |||||
} | |||||
} | |||||
return results; | |||||
} | |||||
bool setBufferUnderrunProtection (const bool shouldBeEnabled) | |||||
{ | |||||
if ([device->device isValid]) | |||||
{ | |||||
device->underrunProtection = shouldBeEnabled; | |||||
return shouldBeEnabled && [[[device->device status] objectForKey: DRDeviceCanUnderrunProtectCDKey] boolValue]; | |||||
} | |||||
return false; | |||||
} | |||||
int getNumAvailableAudioBlocks() const | |||||
{ | |||||
return [[[[device->device status] objectForKey: DRDeviceMediaInfoKey] | |||||
objectForKey: DRDeviceMediaBlocksFreeKey] intValue]; | |||||
} | |||||
ScopedPointer<OpenDiskDevice> device; | |||||
private: | |||||
DiskState lastState; | |||||
AudioCDBurner& owner; | |||||
}; | |||||
//============================================================================== | |||||
AudioCDBurner::AudioCDBurner (const int deviceIndex) | |||||
{ | |||||
pimpl = new Pimpl (*this, deviceIndex); | |||||
} | |||||
AudioCDBurner::~AudioCDBurner() | |||||
{ | |||||
} | |||||
AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex) | |||||
{ | |||||
ScopedPointer <AudioCDBurner> b (new AudioCDBurner (deviceIndex)); | |||||
if (b->pimpl->device == nil) | |||||
b = 0; | |||||
return b.release(); | |||||
} | |||||
namespace | |||||
{ | |||||
NSArray* findDiskBurnerDevices() | |||||
{ | |||||
NSMutableArray* results = [NSMutableArray array]; | |||||
NSArray* devs = [DRDevice devices]; | |||||
for (int i = 0; i < [devs count]; ++i) | |||||
{ | |||||
NSDictionary* dic = [[devs objectAtIndex: i] info]; | |||||
NSString* name = [dic valueForKey: DRDeviceProductNameKey]; | |||||
if (name != nil) | |||||
[results addObject: name]; | |||||
} | |||||
return results; | |||||
} | |||||
} | |||||
StringArray AudioCDBurner::findAvailableDevices() | |||||
{ | |||||
NSArray* names = findDiskBurnerDevices(); | |||||
StringArray s; | |||||
for (unsigned int i = 0; i < [names count]; ++i) | |||||
s.add (CharPointer_UTF8 ([[names objectAtIndex: i] UTF8String])); | |||||
return s; | |||||
} | |||||
AudioCDBurner::DiskState AudioCDBurner::getDiskState() const | |||||
{ | |||||
return pimpl->getDiskState(); | |||||
} | |||||
bool AudioCDBurner::isDiskPresent() const | |||||
{ | |||||
return getDiskState() == writableDiskPresent; | |||||
} | |||||
bool AudioCDBurner::openTray() | |||||
{ | |||||
return pimpl->openTray(); | |||||
} | |||||
AudioCDBurner::DiskState AudioCDBurner::waitUntilStateChange (int timeOutMilliseconds) | |||||
{ | |||||
const int64 timeout = Time::currentTimeMillis() + timeOutMilliseconds; | |||||
DiskState oldState = getDiskState(); | |||||
DiskState newState = oldState; | |||||
while (newState == oldState && Time::currentTimeMillis() < timeout) | |||||
{ | |||||
newState = getDiskState(); | |||||
Thread::sleep (100); | |||||
} | |||||
return newState; | |||||
} | |||||
Array<int> AudioCDBurner::getAvailableWriteSpeeds() const | |||||
{ | |||||
return pimpl->getAvailableWriteSpeeds(); | |||||
} | |||||
bool AudioCDBurner::setBufferUnderrunProtection (const bool shouldBeEnabled) | |||||
{ | |||||
return pimpl->setBufferUnderrunProtection (shouldBeEnabled); | |||||
} | |||||
int AudioCDBurner::getNumAvailableAudioBlocks() const | |||||
{ | |||||
return pimpl->getNumAvailableAudioBlocks(); | |||||
} | |||||
bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamps) | |||||
{ | |||||
if ([pimpl->device->device isValid]) | |||||
{ | |||||
pimpl->device->addSourceTrack (source, numSamps); | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
String AudioCDBurner::burn (AudioCDBurner::BurnProgressListener* listener, | |||||
bool ejectDiscAfterwards, | |||||
bool performFakeBurnForTesting, | |||||
int writeSpeed) | |||||
{ | |||||
if ([pimpl->device->device isValid]) | |||||
return pimpl->device->burn (listener, ejectDiscAfterwards, performFakeBurnForTesting, writeSpeed); | |||||
return "Couldn't open or write to the CD device"; | |||||
} |
@@ -0,0 +1,260 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
namespace CDReaderHelpers | |||||
{ | |||||
inline const XmlElement* getElementForKey (const XmlElement& xml, const String& key) | |||||
{ | |||||
forEachXmlChildElementWithTagName (xml, child, "key") | |||||
if (child->getAllSubText().trim() == key) | |||||
return child->getNextElement(); | |||||
return nullptr; | |||||
} | |||||
static int getIntValueForKey (const XmlElement& xml, const String& key, int defaultValue = -1) | |||||
{ | |||||
const XmlElement* const block = getElementForKey (xml, key); | |||||
return block != nullptr ? block->getAllSubText().trim().getIntValue() : defaultValue; | |||||
} | |||||
// Get the track offsets for a CD given an XmlElement representing its TOC.Plist. | |||||
// Returns NULL on success, otherwise a const char* representing an error. | |||||
static const char* getTrackOffsets (XmlDocument& xmlDocument, Array<int>& offsets) | |||||
{ | |||||
const ScopedPointer<XmlElement> xml (xmlDocument.getDocumentElement()); | |||||
if (xml == nullptr) | |||||
return "Couldn't parse XML in file"; | |||||
const XmlElement* const dict = xml->getChildByName ("dict"); | |||||
if (dict == nullptr) | |||||
return "Couldn't get top level dictionary"; | |||||
const XmlElement* const sessions = getElementForKey (*dict, "Sessions"); | |||||
if (sessions == nullptr) | |||||
return "Couldn't find sessions key"; | |||||
const XmlElement* const session = sessions->getFirstChildElement(); | |||||
if (session == nullptr) | |||||
return "Couldn't find first session"; | |||||
const int leadOut = getIntValueForKey (*session, "Leadout Block"); | |||||
if (leadOut < 0) | |||||
return "Couldn't find Leadout Block"; | |||||
const XmlElement* const trackArray = getElementForKey (*session, "Track Array"); | |||||
if (trackArray == nullptr) | |||||
return "Couldn't find Track Array"; | |||||
forEachXmlChildElement (*trackArray, track) | |||||
{ | |||||
const int trackValue = getIntValueForKey (*track, "Start Block"); | |||||
if (trackValue < 0) | |||||
return "Couldn't find Start Block in the track"; | |||||
offsets.add (trackValue * AudioCDReader::samplesPerFrame - 88200); | |||||
} | |||||
offsets.add (leadOut * AudioCDReader::samplesPerFrame - 88200); | |||||
return nullptr; | |||||
} | |||||
static void findDevices (Array<File>& cds) | |||||
{ | |||||
File volumes ("/Volumes"); | |||||
volumes.findChildFiles (cds, File::findDirectories, false); | |||||
for (int i = cds.size(); --i >= 0;) | |||||
if (! cds.getReference(i).getChildFile (".TOC.plist").exists()) | |||||
cds.remove (i); | |||||
} | |||||
struct TrackSorter | |||||
{ | |||||
static int getCDTrackNumber (const File& file) | |||||
{ | |||||
return file.getFileName().initialSectionContainingOnly ("0123456789").getIntValue(); | |||||
} | |||||
static int compareElements (const File& first, const File& second) | |||||
{ | |||||
const int firstTrack = getCDTrackNumber (first); | |||||
const int secondTrack = getCDTrackNumber (second); | |||||
jassert (firstTrack > 0 && secondTrack > 0); | |||||
return firstTrack - secondTrack; | |||||
} | |||||
}; | |||||
} | |||||
//============================================================================== | |||||
StringArray AudioCDReader::getAvailableCDNames() | |||||
{ | |||||
Array<File> cds; | |||||
CDReaderHelpers::findDevices (cds); | |||||
StringArray names; | |||||
for (int i = 0; i < cds.size(); ++i) | |||||
names.add (cds.getReference(i).getFileName()); | |||||
return names; | |||||
} | |||||
AudioCDReader* AudioCDReader::createReaderForCD (const int index) | |||||
{ | |||||
Array<File> cds; | |||||
CDReaderHelpers::findDevices (cds); | |||||
if (cds[index].exists()) | |||||
return new AudioCDReader (cds[index]); | |||||
return nullptr; | |||||
} | |||||
AudioCDReader::AudioCDReader (const File& volume) | |||||
: AudioFormatReader (0, "CD Audio"), | |||||
volumeDir (volume), | |||||
currentReaderTrack (-1), | |||||
reader (0) | |||||
{ | |||||
sampleRate = 44100.0; | |||||
bitsPerSample = 16; | |||||
numChannels = 2; | |||||
usesFloatingPointData = false; | |||||
refreshTrackLengths(); | |||||
} | |||||
AudioCDReader::~AudioCDReader() | |||||
{ | |||||
} | |||||
void AudioCDReader::refreshTrackLengths() | |||||
{ | |||||
tracks.clear(); | |||||
trackStartSamples.clear(); | |||||
lengthInSamples = 0; | |||||
volumeDir.findChildFiles (tracks, File::findFiles | File::ignoreHiddenFiles, false, "*.aiff"); | |||||
CDReaderHelpers::TrackSorter sorter; | |||||
tracks.sort (sorter); | |||||
const File toc (volumeDir.getChildFile (".TOC.plist")); | |||||
if (toc.exists()) | |||||
{ | |||||
XmlDocument doc (toc); | |||||
const char* error = CDReaderHelpers::getTrackOffsets (doc, trackStartSamples); | |||||
(void) error; // could be logged.. | |||||
lengthInSamples = trackStartSamples.getLast() - trackStartSamples.getFirst(); | |||||
} | |||||
} | |||||
bool AudioCDReader::readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | |||||
int64 startSampleInFile, int numSamples) | |||||
{ | |||||
while (numSamples > 0) | |||||
{ | |||||
int track = -1; | |||||
for (int i = 0; i < trackStartSamples.size() - 1; ++i) | |||||
{ | |||||
if (startSampleInFile < trackStartSamples.getUnchecked (i + 1)) | |||||
{ | |||||
track = i; | |||||
break; | |||||
} | |||||
} | |||||
if (track < 0) | |||||
return false; | |||||
if (track != currentReaderTrack) | |||||
{ | |||||
reader = nullptr; | |||||
if (FileInputStream* const in = tracks [track].createInputStream()) | |||||
{ | |||||
BufferedInputStream* const bin = new BufferedInputStream (in, 65536, true); | |||||
AiffAudioFormat format; | |||||
reader = format.createReaderFor (bin, true); | |||||
if (reader == nullptr) | |||||
currentReaderTrack = -1; | |||||
else | |||||
currentReaderTrack = track; | |||||
} | |||||
} | |||||
if (reader == nullptr) | |||||
return false; | |||||
const int startPos = (int) (startSampleInFile - trackStartSamples.getUnchecked (track)); | |||||
const int numAvailable = (int) jmin ((int64) numSamples, reader->lengthInSamples - startPos); | |||||
reader->readSamples (destSamples, numDestChannels, startOffsetInDestBuffer, startPos, numAvailable); | |||||
numSamples -= numAvailable; | |||||
startSampleInFile += numAvailable; | |||||
} | |||||
return true; | |||||
} | |||||
bool AudioCDReader::isCDStillPresent() const | |||||
{ | |||||
return volumeDir.exists(); | |||||
} | |||||
void AudioCDReader::ejectDisk() | |||||
{ | |||||
JUCE_AUTORELEASEPOOL | |||||
[[NSWorkspace sharedWorkspace] unmountAndEjectDeviceAtPath: juceStringToNS (volumeDir.getFullPathName())]; | |||||
} | |||||
bool AudioCDReader::isTrackAudio (int trackNum) const | |||||
{ | |||||
return tracks [trackNum].hasFileExtension (".aiff"); | |||||
} | |||||
void AudioCDReader::enableIndexScanning (bool) | |||||
{ | |||||
// any way to do this on a Mac?? | |||||
} | |||||
int AudioCDReader::getLastIndex() const | |||||
{ | |||||
return 0; | |||||
} | |||||
Array<int> AudioCDReader::findIndexesInTrack (const int /*trackNumber*/) | |||||
{ | |||||
return Array<int>(); | |||||
} |
@@ -0,0 +1,523 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
namespace CoreMidiHelpers | |||||
{ | |||||
static bool logError (const OSStatus err, const int lineNum) | |||||
{ | |||||
if (err == noErr) | |||||
return true; | |||||
Logger::writeToLog ("CoreMidi error: " + String (lineNum) + " - " + String::toHexString ((int) err)); | |||||
jassertfalse; | |||||
return false; | |||||
} | |||||
#undef CHECK_ERROR | |||||
#define CHECK_ERROR(a) CoreMidiHelpers::logError (a, __LINE__) | |||||
//============================================================================== | |||||
static String getMidiObjectName (MIDIObjectRef entity) | |||||
{ | |||||
String result; | |||||
CFStringRef str = 0; | |||||
MIDIObjectGetStringProperty (entity, kMIDIPropertyName, &str); | |||||
if (str != 0) | |||||
{ | |||||
result = String::fromCFString (str); | |||||
CFRelease (str); | |||||
} | |||||
return result; | |||||
} | |||||
static String getEndpointName (MIDIEndpointRef endpoint, bool isExternal) | |||||
{ | |||||
String result (getMidiObjectName (endpoint)); | |||||
MIDIEntityRef entity = 0; | |||||
MIDIEndpointGetEntity (endpoint, &entity); | |||||
if (entity == 0) | |||||
return result; // probably virtual | |||||
if (result.isEmpty()) | |||||
result = getMidiObjectName (entity); // endpoint name is empty - try the entity | |||||
// now consider the device's name | |||||
MIDIDeviceRef device = 0; | |||||
MIDIEntityGetDevice (entity, &device); | |||||
if (device != 0) | |||||
{ | |||||
const String deviceName (getMidiObjectName (device)); | |||||
if (deviceName.isNotEmpty()) | |||||
{ | |||||
// if an external device has only one entity, throw away | |||||
// the endpoint name and just use the device name | |||||
if (isExternal && MIDIDeviceGetNumberOfEntities (device) < 2) | |||||
{ | |||||
result = deviceName; | |||||
} | |||||
else if (! result.startsWithIgnoreCase (deviceName)) | |||||
{ | |||||
// prepend the device name to the entity name | |||||
result = (deviceName + " " + result).trimEnd(); | |||||
} | |||||
} | |||||
} | |||||
return result; | |||||
} | |||||
static String getConnectedEndpointName (MIDIEndpointRef endpoint) | |||||
{ | |||||
String result; | |||||
// Does the endpoint have connections? | |||||
CFDataRef connections = 0; | |||||
int numConnections = 0; | |||||
MIDIObjectGetDataProperty (endpoint, kMIDIPropertyConnectionUniqueID, &connections); | |||||
if (connections != 0) | |||||
{ | |||||
numConnections = ((int) CFDataGetLength (connections)) / (int) sizeof (MIDIUniqueID); | |||||
if (numConnections > 0) | |||||
{ | |||||
const SInt32* pid = reinterpret_cast <const SInt32*> (CFDataGetBytePtr (connections)); | |||||
for (int i = 0; i < numConnections; ++i, ++pid) | |||||
{ | |||||
MIDIUniqueID uid = (MIDIUniqueID) ByteOrder::swapIfLittleEndian ((uint32) *pid); | |||||
MIDIObjectRef connObject; | |||||
MIDIObjectType connObjectType; | |||||
OSStatus err = MIDIObjectFindByUniqueID (uid, &connObject, &connObjectType); | |||||
if (err == noErr) | |||||
{ | |||||
String s; | |||||
if (connObjectType == kMIDIObjectType_ExternalSource | |||||
|| connObjectType == kMIDIObjectType_ExternalDestination) | |||||
{ | |||||
// Connected to an external device's endpoint (10.3 and later). | |||||
s = getEndpointName (static_cast <MIDIEndpointRef> (connObject), true); | |||||
} | |||||
else | |||||
{ | |||||
// Connected to an external device (10.2) (or something else, catch-all) | |||||
s = getMidiObjectName (connObject); | |||||
} | |||||
if (s.isNotEmpty()) | |||||
{ | |||||
if (result.isNotEmpty()) | |||||
result += ", "; | |||||
result += s; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
CFRelease (connections); | |||||
} | |||||
if (result.isEmpty()) // Here, either the endpoint had no connections, or we failed to obtain names for them. | |||||
result = getEndpointName (endpoint, false); | |||||
return result; | |||||
} | |||||
static StringArray findDevices (const bool forInput) | |||||
{ | |||||
const ItemCount num = forInput ? MIDIGetNumberOfSources() | |||||
: MIDIGetNumberOfDestinations(); | |||||
StringArray s; | |||||
for (ItemCount i = 0; i < num; ++i) | |||||
{ | |||||
MIDIEndpointRef dest = forInput ? MIDIGetSource (i) | |||||
: MIDIGetDestination (i); | |||||
String name; | |||||
if (dest != 0) | |||||
name = getConnectedEndpointName (dest); | |||||
if (name.isEmpty()) | |||||
name = "<error>"; | |||||
s.add (name); | |||||
} | |||||
return s; | |||||
} | |||||
static void globalSystemChangeCallback (const MIDINotification*, void*) | |||||
{ | |||||
// TODO.. Should pass-on this notification.. | |||||
} | |||||
static String getGlobalMidiClientName() | |||||
{ | |||||
JUCEApplicationBase* const app = JUCEApplicationBase::getInstance(); | |||||
return app != nullptr ? app->getApplicationName() : "JUCE"; | |||||
} | |||||
static MIDIClientRef getGlobalMidiClient() | |||||
{ | |||||
static MIDIClientRef globalMidiClient = 0; | |||||
if (globalMidiClient == 0) | |||||
{ | |||||
// Since OSX 10.6, the MIDIClientCreate function will only work | |||||
// correctly when called from the message thread! | |||||
jassert (MessageManager::getInstance()->isThisTheMessageThread()); | |||||
CFStringRef name = getGlobalMidiClientName().toCFString(); | |||||
CHECK_ERROR (MIDIClientCreate (name, &globalSystemChangeCallback, 0, &globalMidiClient)); | |||||
CFRelease (name); | |||||
} | |||||
return globalMidiClient; | |||||
} | |||||
//============================================================================== | |||||
class MidiPortAndEndpoint | |||||
{ | |||||
public: | |||||
MidiPortAndEndpoint (MIDIPortRef port_, MIDIEndpointRef endPoint_) | |||||
: port (port_), endPoint (endPoint_) | |||||
{ | |||||
} | |||||
~MidiPortAndEndpoint() | |||||
{ | |||||
if (port != 0) | |||||
MIDIPortDispose (port); | |||||
if (port == 0 && endPoint != 0) // if port == 0, it means we created the endpoint, so it's safe to delete it | |||||
MIDIEndpointDispose (endPoint); | |||||
} | |||||
void send (const MIDIPacketList* const packets) | |||||
{ | |||||
if (port != 0) | |||||
MIDISend (port, endPoint, packets); | |||||
else | |||||
MIDIReceived (endPoint, packets); | |||||
} | |||||
MIDIPortRef port; | |||||
MIDIEndpointRef endPoint; | |||||
}; | |||||
//============================================================================== | |||||
class MidiPortAndCallback; | |||||
CriticalSection callbackLock; | |||||
Array<MidiPortAndCallback*> activeCallbacks; | |||||
class MidiPortAndCallback | |||||
{ | |||||
public: | |||||
MidiPortAndCallback (MidiInputCallback& callback_) | |||||
: input (nullptr), active (false), callback (callback_), concatenator (2048) | |||||
{ | |||||
} | |||||
~MidiPortAndCallback() | |||||
{ | |||||
active = false; | |||||
{ | |||||
const ScopedLock sl (callbackLock); | |||||
activeCallbacks.removeFirstMatchingValue (this); | |||||
} | |||||
if (portAndEndpoint != nullptr && portAndEndpoint->port != 0) | |||||
CHECK_ERROR (MIDIPortDisconnectSource (portAndEndpoint->port, portAndEndpoint->endPoint)); | |||||
} | |||||
void handlePackets (const MIDIPacketList* const pktlist) | |||||
{ | |||||
const double time = Time::getMillisecondCounterHiRes() * 0.001; | |||||
const ScopedLock sl (callbackLock); | |||||
if (activeCallbacks.contains (this) && active) | |||||
{ | |||||
const MIDIPacket* packet = &pktlist->packet[0]; | |||||
for (unsigned int i = 0; i < pktlist->numPackets; ++i) | |||||
{ | |||||
concatenator.pushMidiData (packet->data, (int) packet->length, time, | |||||
input, callback); | |||||
packet = MIDIPacketNext (packet); | |||||
} | |||||
} | |||||
} | |||||
MidiInput* input; | |||||
ScopedPointer<MidiPortAndEndpoint> portAndEndpoint; | |||||
volatile bool active; | |||||
private: | |||||
MidiInputCallback& callback; | |||||
MidiDataConcatenator concatenator; | |||||
}; | |||||
static void midiInputProc (const MIDIPacketList* pktlist, void* readProcRefCon, void* /*srcConnRefCon*/) | |||||
{ | |||||
static_cast <MidiPortAndCallback*> (readProcRefCon)->handlePackets (pktlist); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
StringArray MidiOutput::getDevices() { return CoreMidiHelpers::findDevices (false); } | |||||
int MidiOutput::getDefaultDeviceIndex() { return 0; } | |||||
MidiOutput* MidiOutput::openDevice (int index) | |||||
{ | |||||
MidiOutput* mo = nullptr; | |||||
if (isPositiveAndBelow (index, (int) MIDIGetNumberOfDestinations())) | |||||
{ | |||||
MIDIEndpointRef endPoint = MIDIGetDestination ((ItemCount) index); | |||||
CFStringRef pname; | |||||
if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &pname))) | |||||
{ | |||||
MIDIClientRef client = CoreMidiHelpers::getGlobalMidiClient(); | |||||
MIDIPortRef port; | |||||
if (client != 0 && CHECK_ERROR (MIDIOutputPortCreate (client, pname, &port))) | |||||
{ | |||||
mo = new MidiOutput(); | |||||
mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (port, endPoint); | |||||
} | |||||
CFRelease (pname); | |||||
} | |||||
} | |||||
return mo; | |||||
} | |||||
MidiOutput* MidiOutput::createNewDevice (const String& deviceName) | |||||
{ | |||||
MidiOutput* mo = nullptr; | |||||
MIDIClientRef client = CoreMidiHelpers::getGlobalMidiClient(); | |||||
MIDIEndpointRef endPoint; | |||||
CFStringRef name = deviceName.toCFString(); | |||||
if (client != 0 && CHECK_ERROR (MIDISourceCreate (client, name, &endPoint))) | |||||
{ | |||||
mo = new MidiOutput(); | |||||
mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (0, endPoint); | |||||
} | |||||
CFRelease (name); | |||||
return mo; | |||||
} | |||||
MidiOutput::~MidiOutput() | |||||
{ | |||||
delete static_cast<CoreMidiHelpers::MidiPortAndEndpoint*> (internal); | |||||
} | |||||
void MidiOutput::sendMessageNow (const MidiMessage& message) | |||||
{ | |||||
#if JUCE_IOS | |||||
const MIDITimeStamp timeStamp = mach_absolute_time(); | |||||
#else | |||||
const MIDITimeStamp timeStamp = AudioGetCurrentHostTime(); | |||||
#endif | |||||
HeapBlock <MIDIPacketList> allocatedPackets; | |||||
MIDIPacketList stackPacket; | |||||
MIDIPacketList* packetToSend = &stackPacket; | |||||
const size_t dataSize = (size_t) message.getRawDataSize(); | |||||
if (message.isSysEx()) | |||||
{ | |||||
const int maxPacketSize = 256; | |||||
int pos = 0, bytesLeft = (int) dataSize; | |||||
const int numPackets = (bytesLeft + maxPacketSize - 1) / maxPacketSize; | |||||
allocatedPackets.malloc ((size_t) (32 * (size_t) numPackets + dataSize), 1); | |||||
packetToSend = allocatedPackets; | |||||
packetToSend->numPackets = (UInt32) numPackets; | |||||
MIDIPacket* p = packetToSend->packet; | |||||
for (int i = 0; i < numPackets; ++i) | |||||
{ | |||||
p->timeStamp = timeStamp; | |||||
p->length = (UInt16) jmin (maxPacketSize, bytesLeft); | |||||
memcpy (p->data, message.getRawData() + pos, p->length); | |||||
pos += p->length; | |||||
bytesLeft -= p->length; | |||||
p = MIDIPacketNext (p); | |||||
} | |||||
} | |||||
else if (dataSize < 65536) // max packet size | |||||
{ | |||||
const size_t stackCapacity = sizeof (stackPacket.packet->data); | |||||
if (dataSize > stackCapacity) | |||||
{ | |||||
allocatedPackets.malloc ((sizeof (MIDIPacketList) - stackCapacity) + dataSize, 1); | |||||
packetToSend = allocatedPackets; | |||||
} | |||||
packetToSend->numPackets = 1; | |||||
MIDIPacket& p = *(packetToSend->packet); | |||||
p.timeStamp = timeStamp; | |||||
p.length = (UInt16) dataSize; | |||||
memcpy (p.data, message.getRawData(), dataSize); | |||||
} | |||||
else | |||||
{ | |||||
jassertfalse; // packet too large to send! | |||||
return; | |||||
} | |||||
static_cast<CoreMidiHelpers::MidiPortAndEndpoint*> (internal)->send (packetToSend); | |||||
} | |||||
//============================================================================== | |||||
StringArray MidiInput::getDevices() { return CoreMidiHelpers::findDevices (true); } | |||||
int MidiInput::getDefaultDeviceIndex() { return 0; } | |||||
MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) | |||||
{ | |||||
jassert (callback != 0); | |||||
using namespace CoreMidiHelpers; | |||||
MidiInput* newInput = nullptr; | |||||
if (isPositiveAndBelow (index, (int) MIDIGetNumberOfSources())) | |||||
{ | |||||
MIDIEndpointRef endPoint = MIDIGetSource ((ItemCount) index); | |||||
if (endPoint != 0) | |||||
{ | |||||
CFStringRef name; | |||||
if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &name))) | |||||
{ | |||||
if (MIDIClientRef client = getGlobalMidiClient()) | |||||
{ | |||||
MIDIPortRef port; | |||||
ScopedPointer <MidiPortAndCallback> mpc (new MidiPortAndCallback (*callback)); | |||||
if (CHECK_ERROR (MIDIInputPortCreate (client, name, midiInputProc, mpc, &port))) | |||||
{ | |||||
if (CHECK_ERROR (MIDIPortConnectSource (port, endPoint, 0))) | |||||
{ | |||||
mpc->portAndEndpoint = new MidiPortAndEndpoint (port, endPoint); | |||||
newInput = new MidiInput (getDevices() [index]); | |||||
mpc->input = newInput; | |||||
newInput->internal = mpc; | |||||
const ScopedLock sl (callbackLock); | |||||
activeCallbacks.add (mpc.release()); | |||||
} | |||||
else | |||||
{ | |||||
CHECK_ERROR (MIDIPortDispose (port)); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
CFRelease (name); | |||||
} | |||||
} | |||||
return newInput; | |||||
} | |||||
MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) | |||||
{ | |||||
jassert (callback != nullptr); | |||||
using namespace CoreMidiHelpers; | |||||
MidiInput* mi = nullptr; | |||||
if (MIDIClientRef client = getGlobalMidiClient()) | |||||
{ | |||||
ScopedPointer <MidiPortAndCallback> mpc (new MidiPortAndCallback (*callback)); | |||||
mpc->active = false; | |||||
MIDIEndpointRef endPoint; | |||||
CFStringRef name = deviceName.toCFString(); | |||||
if (CHECK_ERROR (MIDIDestinationCreate (client, name, midiInputProc, mpc, &endPoint))) | |||||
{ | |||||
mpc->portAndEndpoint = new MidiPortAndEndpoint (0, endPoint); | |||||
mi = new MidiInput (deviceName); | |||||
mpc->input = mi; | |||||
mi->internal = mpc; | |||||
const ScopedLock sl (callbackLock); | |||||
activeCallbacks.add (mpc.release()); | |||||
} | |||||
CFRelease (name); | |||||
} | |||||
return mi; | |||||
} | |||||
MidiInput::MidiInput (const String& name_) | |||||
: name (name_) | |||||
{ | |||||
} | |||||
MidiInput::~MidiInput() | |||||
{ | |||||
delete static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal); | |||||
} | |||||
void MidiInput::start() | |||||
{ | |||||
const ScopedLock sl (CoreMidiHelpers::callbackLock); | |||||
static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal)->active = true; | |||||
} | |||||
void MidiInput::stop() | |||||
{ | |||||
const ScopedLock sl (CoreMidiHelpers::callbackLock); | |||||
static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal)->active = false; | |||||
} | |||||
#undef CHECK_ERROR |
@@ -0,0 +1,412 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
namespace CDBurnerHelpers | |||||
{ | |||||
IDiscRecorder* enumCDBurners (StringArray* list, int indexToOpen, IDiscMaster** master) | |||||
{ | |||||
CoInitialize (0); | |||||
IDiscMaster* dm; | |||||
IDiscRecorder* result = nullptr; | |||||
if (SUCCEEDED (CoCreateInstance (CLSID_MSDiscMasterObj, 0, | |||||
CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER, | |||||
IID_IDiscMaster, | |||||
(void**) &dm))) | |||||
{ | |||||
if (SUCCEEDED (dm->Open())) | |||||
{ | |||||
IEnumDiscRecorders* drEnum = nullptr; | |||||
if (SUCCEEDED (dm->EnumDiscRecorders (&drEnum))) | |||||
{ | |||||
IDiscRecorder* dr = nullptr; | |||||
DWORD dummy; | |||||
int index = 0; | |||||
while (drEnum->Next (1, &dr, &dummy) == S_OK) | |||||
{ | |||||
if (indexToOpen == index) | |||||
{ | |||||
result = dr; | |||||
break; | |||||
} | |||||
else if (list != nullptr) | |||||
{ | |||||
BSTR path; | |||||
if (SUCCEEDED (dr->GetPath (&path))) | |||||
list->add ((const WCHAR*) path); | |||||
} | |||||
++index; | |||||
dr->Release(); | |||||
} | |||||
drEnum->Release(); | |||||
} | |||||
if (master == 0) | |||||
dm->Close(); | |||||
} | |||||
if (master != nullptr) | |||||
*master = dm; | |||||
else | |||||
dm->Release(); | |||||
} | |||||
return result; | |||||
} | |||||
} | |||||
//============================================================================== | |||||
class AudioCDBurner::Pimpl : public ComBaseClassHelper <IDiscMasterProgressEvents>, | |||||
public Timer | |||||
{ | |||||
public: | |||||
Pimpl (AudioCDBurner& owner_, IDiscMaster* discMaster_, IDiscRecorder* discRecorder_) | |||||
: owner (owner_), discMaster (discMaster_), discRecorder (discRecorder_), redbook (0), | |||||
listener (0), progress (0), shouldCancel (false) | |||||
{ | |||||
HRESULT hr = discMaster->SetActiveDiscMasterFormat (IID_IRedbookDiscMaster, (void**) &redbook); | |||||
jassert (SUCCEEDED (hr)); | |||||
hr = discMaster->SetActiveDiscRecorder (discRecorder); | |||||
//jassert (SUCCEEDED (hr)); | |||||
lastState = getDiskState(); | |||||
startTimer (2000); | |||||
} | |||||
~Pimpl() {} | |||||
void releaseObjects() | |||||
{ | |||||
discRecorder->Close(); | |||||
if (redbook != nullptr) | |||||
redbook->Release(); | |||||
discRecorder->Release(); | |||||
discMaster->Release(); | |||||
Release(); | |||||
} | |||||
JUCE_COMRESULT QueryCancel (boolean* pbCancel) | |||||
{ | |||||
if (listener != nullptr && ! shouldCancel) | |||||
shouldCancel = listener->audioCDBurnProgress (progress); | |||||
*pbCancel = shouldCancel; | |||||
return S_OK; | |||||
} | |||||
JUCE_COMRESULT NotifyBlockProgress (long nCompleted, long nTotal) | |||||
{ | |||||
progress = nCompleted / (float) nTotal; | |||||
shouldCancel = listener != nullptr && listener->audioCDBurnProgress (progress); | |||||
return E_NOTIMPL; | |||||
} | |||||
JUCE_COMRESULT NotifyPnPActivity (void) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT NotifyAddProgress (long /*nCompletedSteps*/, long /*nTotalSteps*/) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT NotifyTrackProgress (long /*nCurrentTrack*/, long /*nTotalTracks*/) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT NotifyPreparingBurn (long /*nEstimatedSeconds*/) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT NotifyClosingDisc (long /*nEstimatedSeconds*/) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT NotifyBurnComplete (HRESULT /*status*/) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT NotifyEraseComplete (HRESULT /*status*/) { return E_NOTIMPL; } | |||||
class ScopedDiscOpener | |||||
{ | |||||
public: | |||||
ScopedDiscOpener (Pimpl& p) : pimpl (p) { pimpl.discRecorder->OpenExclusive(); } | |||||
~ScopedDiscOpener() { pimpl.discRecorder->Close(); } | |||||
private: | |||||
Pimpl& pimpl; | |||||
JUCE_DECLARE_NON_COPYABLE (ScopedDiscOpener) | |||||
}; | |||||
DiskState getDiskState() | |||||
{ | |||||
const ScopedDiscOpener opener (*this); | |||||
long type, flags; | |||||
HRESULT hr = discRecorder->QueryMediaType (&type, &flags); | |||||
if (FAILED (hr)) | |||||
return unknown; | |||||
if (type != 0 && (flags & MEDIA_WRITABLE) != 0) | |||||
return writableDiskPresent; | |||||
if (type == 0) | |||||
return noDisc; | |||||
return readOnlyDiskPresent; | |||||
} | |||||
int getIntProperty (const LPOLESTR name, const int defaultReturn) const | |||||
{ | |||||
ComSmartPtr<IPropertyStorage> prop; | |||||
if (FAILED (discRecorder->GetRecorderProperties (prop.resetAndGetPointerAddress()))) | |||||
return defaultReturn; | |||||
PROPSPEC iPropSpec; | |||||
iPropSpec.ulKind = PRSPEC_LPWSTR; | |||||
iPropSpec.lpwstr = name; | |||||
PROPVARIANT iPropVariant; | |||||
return FAILED (prop->ReadMultiple (1, &iPropSpec, &iPropVariant)) | |||||
? defaultReturn : (int) iPropVariant.lVal; | |||||
} | |||||
bool setIntProperty (const LPOLESTR name, const int value) const | |||||
{ | |||||
ComSmartPtr<IPropertyStorage> prop; | |||||
if (FAILED (discRecorder->GetRecorderProperties (prop.resetAndGetPointerAddress()))) | |||||
return false; | |||||
PROPSPEC iPropSpec; | |||||
iPropSpec.ulKind = PRSPEC_LPWSTR; | |||||
iPropSpec.lpwstr = name; | |||||
PROPVARIANT iPropVariant; | |||||
if (FAILED (prop->ReadMultiple (1, &iPropSpec, &iPropVariant))) | |||||
return false; | |||||
iPropVariant.lVal = (long) value; | |||||
return SUCCEEDED (prop->WriteMultiple (1, &iPropSpec, &iPropVariant, iPropVariant.vt)) | |||||
&& SUCCEEDED (discRecorder->SetRecorderProperties (prop)); | |||||
} | |||||
void timerCallback() | |||||
{ | |||||
const DiskState state = getDiskState(); | |||||
if (state != lastState) | |||||
{ | |||||
lastState = state; | |||||
owner.sendChangeMessage(); | |||||
} | |||||
} | |||||
AudioCDBurner& owner; | |||||
DiskState lastState; | |||||
IDiscMaster* discMaster; | |||||
IDiscRecorder* discRecorder; | |||||
IRedbookDiscMaster* redbook; | |||||
AudioCDBurner::BurnProgressListener* listener; | |||||
float progress; | |||||
bool shouldCancel; | |||||
}; | |||||
//============================================================================== | |||||
AudioCDBurner::AudioCDBurner (const int deviceIndex) | |||||
{ | |||||
IDiscMaster* discMaster = nullptr; | |||||
IDiscRecorder* discRecorder = CDBurnerHelpers::enumCDBurners (0, deviceIndex, &discMaster); | |||||
if (discRecorder != nullptr) | |||||
pimpl = new Pimpl (*this, discMaster, discRecorder); | |||||
} | |||||
AudioCDBurner::~AudioCDBurner() | |||||
{ | |||||
if (pimpl != nullptr) | |||||
pimpl.release()->releaseObjects(); | |||||
} | |||||
StringArray AudioCDBurner::findAvailableDevices() | |||||
{ | |||||
StringArray devs; | |||||
CDBurnerHelpers::enumCDBurners (&devs, -1, 0); | |||||
return devs; | |||||
} | |||||
AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex) | |||||
{ | |||||
ScopedPointer<AudioCDBurner> b (new AudioCDBurner (deviceIndex)); | |||||
if (b->pimpl == 0) | |||||
b = nullptr; | |||||
return b.release(); | |||||
} | |||||
AudioCDBurner::DiskState AudioCDBurner::getDiskState() const | |||||
{ | |||||
return pimpl->getDiskState(); | |||||
} | |||||
bool AudioCDBurner::isDiskPresent() const | |||||
{ | |||||
return getDiskState() == writableDiskPresent; | |||||
} | |||||
bool AudioCDBurner::openTray() | |||||
{ | |||||
const Pimpl::ScopedDiscOpener opener (*pimpl); | |||||
return SUCCEEDED (pimpl->discRecorder->Eject()); | |||||
} | |||||
AudioCDBurner::DiskState AudioCDBurner::waitUntilStateChange (int timeOutMilliseconds) | |||||
{ | |||||
const int64 timeout = Time::currentTimeMillis() + timeOutMilliseconds; | |||||
DiskState oldState = getDiskState(); | |||||
DiskState newState = oldState; | |||||
while (newState == oldState && Time::currentTimeMillis() < timeout) | |||||
{ | |||||
newState = getDiskState(); | |||||
Thread::sleep (jmin (250, (int) (timeout - Time::currentTimeMillis()))); | |||||
} | |||||
return newState; | |||||
} | |||||
Array<int> AudioCDBurner::getAvailableWriteSpeeds() const | |||||
{ | |||||
Array<int> results; | |||||
const int maxSpeed = pimpl->getIntProperty (L"MaxWriteSpeed", 1); | |||||
const int speeds[] = { 1, 2, 4, 8, 12, 16, 20, 24, 32, 40, 64, 80 }; | |||||
for (int i = 0; i < numElementsInArray (speeds); ++i) | |||||
if (speeds[i] <= maxSpeed) | |||||
results.add (speeds[i]); | |||||
results.addIfNotAlreadyThere (maxSpeed); | |||||
return results; | |||||
} | |||||
bool AudioCDBurner::setBufferUnderrunProtection (const bool shouldBeEnabled) | |||||
{ | |||||
if (pimpl->getIntProperty (L"BufferUnderrunFreeCapable", 0) == 0) | |||||
return false; | |||||
pimpl->setIntProperty (L"EnableBufferUnderrunFree", shouldBeEnabled ? -1 : 0); | |||||
return pimpl->getIntProperty (L"EnableBufferUnderrunFree", 0) != 0; | |||||
} | |||||
int AudioCDBurner::getNumAvailableAudioBlocks() const | |||||
{ | |||||
long blocksFree = 0; | |||||
pimpl->redbook->GetAvailableAudioTrackBlocks (&blocksFree); | |||||
return blocksFree; | |||||
} | |||||
String AudioCDBurner::burn (AudioCDBurner::BurnProgressListener* listener, bool ejectDiscAfterwards, | |||||
bool performFakeBurnForTesting, int writeSpeed) | |||||
{ | |||||
pimpl->setIntProperty (L"WriteSpeed", writeSpeed > 0 ? writeSpeed : -1); | |||||
pimpl->listener = listener; | |||||
pimpl->progress = 0; | |||||
pimpl->shouldCancel = false; | |||||
UINT_PTR cookie; | |||||
HRESULT hr = pimpl->discMaster->ProgressAdvise ((AudioCDBurner::Pimpl*) pimpl, &cookie); | |||||
hr = pimpl->discMaster->RecordDisc (performFakeBurnForTesting, | |||||
ejectDiscAfterwards); | |||||
String error; | |||||
if (hr != S_OK) | |||||
{ | |||||
const char* e = "Couldn't open or write to the CD device"; | |||||
if (hr == IMAPI_E_USERABORT) | |||||
e = "User cancelled the write operation"; | |||||
else if (hr == IMAPI_E_MEDIUM_NOTPRESENT || hr == IMAPI_E_TRACKOPEN) | |||||
e = "No Disk present"; | |||||
error = e; | |||||
} | |||||
pimpl->discMaster->ProgressUnadvise (cookie); | |||||
pimpl->listener = 0; | |||||
return error; | |||||
} | |||||
bool AudioCDBurner::addAudioTrack (AudioSource* audioSource, int numSamples) | |||||
{ | |||||
if (audioSource == 0) | |||||
return false; | |||||
ScopedPointer<AudioSource> source (audioSource); | |||||
long bytesPerBlock; | |||||
HRESULT hr = pimpl->redbook->GetAudioBlockSize (&bytesPerBlock); | |||||
const int samplesPerBlock = bytesPerBlock / 4; | |||||
bool ok = true; | |||||
hr = pimpl->redbook->CreateAudioTrack ((long) numSamples / (bytesPerBlock * 4)); | |||||
HeapBlock <byte> buffer (bytesPerBlock); | |||||
AudioSampleBuffer sourceBuffer (2, samplesPerBlock); | |||||
int samplesDone = 0; | |||||
source->prepareToPlay (samplesPerBlock, 44100.0); | |||||
while (ok) | |||||
{ | |||||
{ | |||||
AudioSourceChannelInfo info (&sourceBuffer, 0, samplesPerBlock); | |||||
sourceBuffer.clear(); | |||||
source->getNextAudioBlock (info); | |||||
} | |||||
buffer.clear (bytesPerBlock); | |||||
typedef AudioData::Pointer <AudioData::Int16, AudioData::LittleEndian, | |||||
AudioData::Interleaved, AudioData::NonConst> CDSampleFormat; | |||||
typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, | |||||
AudioData::NonInterleaved, AudioData::Const> SourceSampleFormat; | |||||
CDSampleFormat left (buffer, 2); | |||||
left.convertSamples (SourceSampleFormat (sourceBuffer.getSampleData (0)), samplesPerBlock); | |||||
CDSampleFormat right (buffer + 2, 2); | |||||
right.convertSamples (SourceSampleFormat (sourceBuffer.getSampleData (1)), samplesPerBlock); | |||||
hr = pimpl->redbook->AddAudioTrackBlocks (buffer, bytesPerBlock); | |||||
if (FAILED (hr)) | |||||
ok = false; | |||||
samplesDone += samplesPerBlock; | |||||
if (samplesDone >= numSamples) | |||||
break; | |||||
} | |||||
hr = pimpl->redbook->CloseAudioTrack(); | |||||
return ok && hr == S_OK; | |||||
} |
@@ -0,0 +1,482 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
class MidiInCollector | |||||
{ | |||||
public: | |||||
MidiInCollector (MidiInput* const input_, | |||||
MidiInputCallback& callback_) | |||||
: deviceHandle (0), | |||||
input (input_), | |||||
callback (callback_), | |||||
concatenator (4096), | |||||
isStarted (false), | |||||
startTime (0) | |||||
{ | |||||
} | |||||
~MidiInCollector() | |||||
{ | |||||
stop(); | |||||
if (deviceHandle != 0) | |||||
{ | |||||
for (int count = 5; --count >= 0;) | |||||
{ | |||||
if (midiInClose (deviceHandle) == MMSYSERR_NOERROR) | |||||
break; | |||||
Sleep (20); | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void handleMessage (const uint8* bytes, const uint32 timeStamp) | |||||
{ | |||||
if (bytes[0] >= 0x80 && isStarted) | |||||
{ | |||||
concatenator.pushMidiData (bytes, MidiMessage::getMessageLengthFromFirstByte (bytes[0]), | |||||
convertTimeStamp (timeStamp), input, callback); | |||||
writeFinishedBlocks(); | |||||
} | |||||
} | |||||
void handleSysEx (MIDIHDR* const hdr, const uint32 timeStamp) | |||||
{ | |||||
if (isStarted && hdr->dwBytesRecorded > 0) | |||||
{ | |||||
concatenator.pushMidiData (hdr->lpData, (int) hdr->dwBytesRecorded, | |||||
convertTimeStamp (timeStamp), input, callback); | |||||
writeFinishedBlocks(); | |||||
} | |||||
} | |||||
void start() | |||||
{ | |||||
if (deviceHandle != 0 && ! isStarted) | |||||
{ | |||||
activeMidiCollectors.addIfNotAlreadyThere (this); | |||||
for (int i = 0; i < (int) numHeaders; ++i) | |||||
headers[i].write (deviceHandle); | |||||
startTime = Time::getMillisecondCounterHiRes(); | |||||
MMRESULT res = midiInStart (deviceHandle); | |||||
if (res == MMSYSERR_NOERROR) | |||||
{ | |||||
concatenator.reset(); | |||||
isStarted = true; | |||||
} | |||||
else | |||||
{ | |||||
unprepareAllHeaders(); | |||||
} | |||||
} | |||||
} | |||||
void stop() | |||||
{ | |||||
if (isStarted) | |||||
{ | |||||
isStarted = false; | |||||
midiInReset (deviceHandle); | |||||
midiInStop (deviceHandle); | |||||
activeMidiCollectors.removeFirstMatchingValue (this); | |||||
unprepareAllHeaders(); | |||||
concatenator.reset(); | |||||
} | |||||
} | |||||
static void CALLBACK midiInCallback (HMIDIIN, UINT uMsg, DWORD_PTR dwInstance, | |||||
DWORD_PTR midiMessage, DWORD_PTR timeStamp) | |||||
{ | |||||
MidiInCollector* const collector = reinterpret_cast <MidiInCollector*> (dwInstance); | |||||
if (activeMidiCollectors.contains (collector)) | |||||
{ | |||||
if (uMsg == MIM_DATA) | |||||
collector->handleMessage ((const uint8*) &midiMessage, (uint32) timeStamp); | |||||
else if (uMsg == MIM_LONGDATA) | |||||
collector->handleSysEx ((MIDIHDR*) midiMessage, (uint32) timeStamp); | |||||
} | |||||
} | |||||
HMIDIIN deviceHandle; | |||||
private: | |||||
static Array <MidiInCollector*, CriticalSection> activeMidiCollectors; | |||||
MidiInput* input; | |||||
MidiInputCallback& callback; | |||||
MidiDataConcatenator concatenator; | |||||
bool volatile isStarted; | |||||
double startTime; | |||||
class MidiHeader | |||||
{ | |||||
public: | |||||
MidiHeader() | |||||
{ | |||||
zerostruct (hdr); | |||||
hdr.lpData = data; | |||||
hdr.dwBufferLength = (DWORD) numElementsInArray (data); | |||||
} | |||||
void write (HMIDIIN deviceHandle) | |||||
{ | |||||
hdr.dwBytesRecorded = 0; | |||||
MMRESULT res = midiInPrepareHeader (deviceHandle, &hdr, sizeof (hdr)); | |||||
res = midiInAddBuffer (deviceHandle, &hdr, sizeof (hdr)); | |||||
} | |||||
void writeIfFinished (HMIDIIN deviceHandle) | |||||
{ | |||||
if ((hdr.dwFlags & WHDR_DONE) != 0) | |||||
{ | |||||
MMRESULT res = midiInUnprepareHeader (deviceHandle, &hdr, sizeof (hdr)); | |||||
(void) res; | |||||
write (deviceHandle); | |||||
} | |||||
} | |||||
void unprepare (HMIDIIN deviceHandle) | |||||
{ | |||||
if ((hdr.dwFlags & WHDR_DONE) != 0) | |||||
{ | |||||
int c = 10; | |||||
while (--c >= 0 && midiInUnprepareHeader (deviceHandle, &hdr, sizeof (hdr)) == MIDIERR_STILLPLAYING) | |||||
Thread::sleep (20); | |||||
jassert (c >= 0); | |||||
} | |||||
} | |||||
private: | |||||
MIDIHDR hdr; | |||||
char data [256]; | |||||
JUCE_DECLARE_NON_COPYABLE (MidiHeader) | |||||
}; | |||||
enum { numHeaders = 32 }; | |||||
MidiHeader headers [numHeaders]; | |||||
void writeFinishedBlocks() | |||||
{ | |||||
for (int i = 0; i < (int) numHeaders; ++i) | |||||
headers[i].writeIfFinished (deviceHandle); | |||||
} | |||||
void unprepareAllHeaders() | |||||
{ | |||||
for (int i = 0; i < (int) numHeaders; ++i) | |||||
headers[i].unprepare (deviceHandle); | |||||
} | |||||
double convertTimeStamp (uint32 timeStamp) | |||||
{ | |||||
double t = startTime + timeStamp; | |||||
const double now = Time::getMillisecondCounterHiRes(); | |||||
if (t > now) | |||||
{ | |||||
if (t > now + 2.0) | |||||
startTime -= 1.0; | |||||
t = now; | |||||
} | |||||
return t * 0.001; | |||||
} | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInCollector) | |||||
}; | |||||
Array <MidiInCollector*, CriticalSection> MidiInCollector::activeMidiCollectors; | |||||
//============================================================================== | |||||
StringArray MidiInput::getDevices() | |||||
{ | |||||
StringArray s; | |||||
const UINT num = midiInGetNumDevs(); | |||||
for (UINT i = 0; i < num; ++i) | |||||
{ | |||||
MIDIINCAPS mc = { 0 }; | |||||
if (midiInGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) | |||||
s.add (String (mc.szPname, sizeof (mc.szPname))); | |||||
} | |||||
return s; | |||||
} | |||||
int MidiInput::getDefaultDeviceIndex() | |||||
{ | |||||
return 0; | |||||
} | |||||
MidiInput* MidiInput::openDevice (const int index, MidiInputCallback* const callback) | |||||
{ | |||||
if (callback == nullptr) | |||||
return nullptr; | |||||
UINT deviceId = MIDI_MAPPER; | |||||
int n = 0; | |||||
String name; | |||||
const UINT num = midiInGetNumDevs(); | |||||
for (UINT i = 0; i < num; ++i) | |||||
{ | |||||
MIDIINCAPS mc = { 0 }; | |||||
if (midiInGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) | |||||
{ | |||||
if (index == n) | |||||
{ | |||||
deviceId = i; | |||||
name = String (mc.szPname, (size_t) numElementsInArray (mc.szPname)); | |||||
break; | |||||
} | |||||
++n; | |||||
} | |||||
} | |||||
ScopedPointer <MidiInput> in (new MidiInput (name)); | |||||
ScopedPointer <MidiInCollector> collector (new MidiInCollector (in, *callback)); | |||||
HMIDIIN h; | |||||
MMRESULT err = midiInOpen (&h, deviceId, | |||||
(DWORD_PTR) &MidiInCollector::midiInCallback, | |||||
(DWORD_PTR) (MidiInCollector*) collector, | |||||
CALLBACK_FUNCTION); | |||||
if (err == MMSYSERR_NOERROR) | |||||
{ | |||||
collector->deviceHandle = h; | |||||
in->internal = collector.release(); | |||||
return in.release(); | |||||
} | |||||
return nullptr; | |||||
} | |||||
MidiInput::MidiInput (const String& name_) | |||||
: name (name_), | |||||
internal (0) | |||||
{ | |||||
} | |||||
MidiInput::~MidiInput() | |||||
{ | |||||
delete static_cast <MidiInCollector*> (internal); | |||||
} | |||||
void MidiInput::start() { static_cast <MidiInCollector*> (internal)->start(); } | |||||
void MidiInput::stop() { static_cast <MidiInCollector*> (internal)->stop(); } | |||||
//============================================================================== | |||||
struct MidiOutHandle | |||||
{ | |||||
int refCount; | |||||
UINT deviceId; | |||||
HMIDIOUT handle; | |||||
static Array<MidiOutHandle*> activeHandles; | |||||
private: | |||||
JUCE_LEAK_DETECTOR (MidiOutHandle) | |||||
}; | |||||
Array<MidiOutHandle*> MidiOutHandle::activeHandles; | |||||
//============================================================================== | |||||
StringArray MidiOutput::getDevices() | |||||
{ | |||||
StringArray s; | |||||
const UINT num = midiOutGetNumDevs(); | |||||
for (UINT i = 0; i < num; ++i) | |||||
{ | |||||
MIDIOUTCAPS mc = { 0 }; | |||||
if (midiOutGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) | |||||
s.add (String (mc.szPname, sizeof (mc.szPname))); | |||||
} | |||||
return s; | |||||
} | |||||
int MidiOutput::getDefaultDeviceIndex() | |||||
{ | |||||
const UINT num = midiOutGetNumDevs(); | |||||
int n = 0; | |||||
for (UINT i = 0; i < num; ++i) | |||||
{ | |||||
MIDIOUTCAPS mc = { 0 }; | |||||
if (midiOutGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) | |||||
{ | |||||
if ((mc.wTechnology & MOD_MAPPER) != 0) | |||||
return n; | |||||
++n; | |||||
} | |||||
} | |||||
return 0; | |||||
} | |||||
MidiOutput* MidiOutput::openDevice (int index) | |||||
{ | |||||
UINT deviceId = MIDI_MAPPER; | |||||
const UINT num = midiOutGetNumDevs(); | |||||
int n = 0; | |||||
for (UINT i = 0; i < num; ++i) | |||||
{ | |||||
MIDIOUTCAPS mc = { 0 }; | |||||
if (midiOutGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) | |||||
{ | |||||
// use the microsoft sw synth as a default - best not to allow deviceId | |||||
// to be MIDI_MAPPER, or else device sharing breaks | |||||
if (String (mc.szPname, sizeof (mc.szPname)).containsIgnoreCase ("microsoft")) | |||||
deviceId = i; | |||||
if (index == n) | |||||
{ | |||||
deviceId = i; | |||||
break; | |||||
} | |||||
++n; | |||||
} | |||||
} | |||||
for (int i = MidiOutHandle::activeHandles.size(); --i >= 0;) | |||||
{ | |||||
MidiOutHandle* const han = MidiOutHandle::activeHandles.getUnchecked(i); | |||||
if (han->deviceId == deviceId) | |||||
{ | |||||
han->refCount++; | |||||
MidiOutput* const out = new MidiOutput(); | |||||
out->internal = han; | |||||
return out; | |||||
} | |||||
} | |||||
for (int i = 4; --i >= 0;) | |||||
{ | |||||
HMIDIOUT h = 0; | |||||
MMRESULT res = midiOutOpen (&h, deviceId, 0, 0, CALLBACK_NULL); | |||||
if (res == MMSYSERR_NOERROR) | |||||
{ | |||||
MidiOutHandle* const han = new MidiOutHandle(); | |||||
han->deviceId = deviceId; | |||||
han->refCount = 1; | |||||
han->handle = h; | |||||
MidiOutHandle::activeHandles.add (han); | |||||
MidiOutput* const out = new MidiOutput(); | |||||
out->internal = han; | |||||
return out; | |||||
} | |||||
else if (res == MMSYSERR_ALLOCATED) | |||||
{ | |||||
Sleep (100); | |||||
} | |||||
else | |||||
{ | |||||
break; | |||||
} | |||||
} | |||||
return nullptr; | |||||
} | |||||
MidiOutput::~MidiOutput() | |||||
{ | |||||
stopBackgroundThread(); | |||||
MidiOutHandle* const h = static_cast <MidiOutHandle*> (internal); | |||||
if (MidiOutHandle::activeHandles.contains (h) && --(h->refCount) == 0) | |||||
{ | |||||
midiOutClose (h->handle); | |||||
MidiOutHandle::activeHandles.removeFirstMatchingValue (h); | |||||
delete h; | |||||
} | |||||
} | |||||
void MidiOutput::sendMessageNow (const MidiMessage& message) | |||||
{ | |||||
const MidiOutHandle* const handle = static_cast <const MidiOutHandle*> (internal); | |||||
if (message.getRawDataSize() > 3 || message.isSysEx()) | |||||
{ | |||||
MIDIHDR h = { 0 }; | |||||
h.lpData = (char*) message.getRawData(); | |||||
h.dwBytesRecorded = h.dwBufferLength = (DWORD) message.getRawDataSize(); | |||||
if (midiOutPrepareHeader (handle->handle, &h, sizeof (MIDIHDR)) == MMSYSERR_NOERROR) | |||||
{ | |||||
MMRESULT res = midiOutLongMsg (handle->handle, &h, sizeof (MIDIHDR)); | |||||
if (res == MMSYSERR_NOERROR) | |||||
{ | |||||
while ((h.dwFlags & MHDR_DONE) == 0) | |||||
Sleep (1); | |||||
int count = 500; // 1 sec timeout | |||||
while (--count >= 0) | |||||
{ | |||||
res = midiOutUnprepareHeader (handle->handle, &h, sizeof (MIDIHDR)); | |||||
if (res == MIDIERR_STILLPLAYING) | |||||
Sleep (2); | |||||
else | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
midiOutShortMsg (handle->handle, *(unsigned int*) message.getRawData()); | |||||
} | |||||
} |
@@ -0,0 +1,184 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
AudioSourcePlayer::AudioSourcePlayer() | |||||
: source (nullptr), | |||||
sampleRate (0), | |||||
bufferSize (0), | |||||
tempBuffer (2, 8), | |||||
lastGain (1.0f), | |||||
gain (1.0f) | |||||
{ | |||||
} | |||||
AudioSourcePlayer::~AudioSourcePlayer() | |||||
{ | |||||
setSource (nullptr); | |||||
} | |||||
void AudioSourcePlayer::setSource (AudioSource* newSource) | |||||
{ | |||||
if (source != newSource) | |||||
{ | |||||
AudioSource* const oldSource = source; | |||||
if (newSource != nullptr && bufferSize > 0 && sampleRate > 0) | |||||
newSource->prepareToPlay (bufferSize, sampleRate); | |||||
{ | |||||
const ScopedLock sl (readLock); | |||||
source = newSource; | |||||
} | |||||
if (oldSource != nullptr) | |||||
oldSource->releaseResources(); | |||||
} | |||||
} | |||||
void AudioSourcePlayer::setGain (const float newGain) noexcept | |||||
{ | |||||
gain = newGain; | |||||
} | |||||
void AudioSourcePlayer::audioDeviceIOCallback (const float** inputChannelData, | |||||
int totalNumInputChannels, | |||||
float** outputChannelData, | |||||
int totalNumOutputChannels, | |||||
int numSamples) | |||||
{ | |||||
// these should have been prepared by audioDeviceAboutToStart()... | |||||
jassert (sampleRate > 0 && bufferSize > 0); | |||||
const ScopedLock sl (readLock); | |||||
if (source != nullptr) | |||||
{ | |||||
int numActiveChans = 0, numInputs = 0, numOutputs = 0; | |||||
// messy stuff needed to compact the channels down into an array | |||||
// of non-zero pointers.. | |||||
for (int i = 0; i < totalNumInputChannels; ++i) | |||||
{ | |||||
if (inputChannelData[i] != nullptr) | |||||
{ | |||||
inputChans [numInputs++] = inputChannelData[i]; | |||||
if (numInputs >= numElementsInArray (inputChans)) | |||||
break; | |||||
} | |||||
} | |||||
for (int i = 0; i < totalNumOutputChannels; ++i) | |||||
{ | |||||
if (outputChannelData[i] != nullptr) | |||||
{ | |||||
outputChans [numOutputs++] = outputChannelData[i]; | |||||
if (numOutputs >= numElementsInArray (outputChans)) | |||||
break; | |||||
} | |||||
} | |||||
if (numInputs > numOutputs) | |||||
{ | |||||
// if there aren't enough output channels for the number of | |||||
// inputs, we need to create some temporary extra ones (can't | |||||
// use the input data in case it gets written to) | |||||
tempBuffer.setSize (numInputs - numOutputs, numSamples, | |||||
false, false, true); | |||||
for (int i = 0; i < numOutputs; ++i) | |||||
{ | |||||
channels[numActiveChans] = outputChans[i]; | |||||
memcpy (channels[numActiveChans], inputChans[i], sizeof (float) * (size_t) numSamples); | |||||
++numActiveChans; | |||||
} | |||||
for (int i = numOutputs; i < numInputs; ++i) | |||||
{ | |||||
channels[numActiveChans] = tempBuffer.getSampleData (i - numOutputs, 0); | |||||
memcpy (channels[numActiveChans], inputChans[i], sizeof (float) * (size_t) numSamples); | |||||
++numActiveChans; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
for (int i = 0; i < numInputs; ++i) | |||||
{ | |||||
channels[numActiveChans] = outputChans[i]; | |||||
memcpy (channels[numActiveChans], inputChans[i], sizeof (float) * (size_t) numSamples); | |||||
++numActiveChans; | |||||
} | |||||
for (int i = numInputs; i < numOutputs; ++i) | |||||
{ | |||||
channels[numActiveChans] = outputChans[i]; | |||||
zeromem (channels[numActiveChans], sizeof (float) * (size_t) numSamples); | |||||
++numActiveChans; | |||||
} | |||||
} | |||||
AudioSampleBuffer buffer (channels, numActiveChans, numSamples); | |||||
AudioSourceChannelInfo info (&buffer, 0, numSamples); | |||||
source->getNextAudioBlock (info); | |||||
for (int i = info.buffer->getNumChannels(); --i >= 0;) | |||||
buffer.applyGainRamp (i, info.startSample, info.numSamples, lastGain, gain); | |||||
lastGain = gain; | |||||
} | |||||
else | |||||
{ | |||||
for (int i = 0; i < totalNumOutputChannels; ++i) | |||||
if (outputChannelData[i] != nullptr) | |||||
zeromem (outputChannelData[i], sizeof (float) * (size_t) numSamples); | |||||
} | |||||
} | |||||
void AudioSourcePlayer::audioDeviceAboutToStart (AudioIODevice* device) | |||||
{ | |||||
prepareToPlay (device->getCurrentSampleRate(), | |||||
device->getCurrentBufferSizeSamples()); | |||||
} | |||||
void AudioSourcePlayer::prepareToPlay (double newSampleRate, int newBufferSize) | |||||
{ | |||||
sampleRate = newSampleRate; | |||||
bufferSize = newBufferSize; | |||||
zeromem (channels, sizeof (channels)); | |||||
if (source != nullptr) | |||||
source->prepareToPlay (bufferSize, sampleRate); | |||||
} | |||||
void AudioSourcePlayer::audioDeviceStopped() | |||||
{ | |||||
if (source != nullptr) | |||||
source->releaseResources(); | |||||
sampleRate = 0.0; | |||||
bufferSize = 0; | |||||
tempBuffer.setSize (2, 8); | |||||
} |
@@ -0,0 +1,117 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_AUDIOSOURCEPLAYER_JUCEHEADER__ | |||||
#define __JUCE_AUDIOSOURCEPLAYER_JUCEHEADER__ | |||||
//============================================================================== | |||||
/** | |||||
Wrapper class to continuously stream audio from an audio source to an | |||||
AudioIODevice. | |||||
This object acts as an AudioIODeviceCallback, so can be attached to an | |||||
output device, and will stream audio from an AudioSource. | |||||
*/ | |||||
class JUCE_API AudioSourcePlayer : public AudioIODeviceCallback | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an empty AudioSourcePlayer. */ | |||||
AudioSourcePlayer(); | |||||
/** Destructor. | |||||
Make sure this object isn't still being used by an AudioIODevice before | |||||
deleting it! | |||||
*/ | |||||
virtual ~AudioSourcePlayer(); | |||||
//============================================================================== | |||||
/** Changes the current audio source to play from. | |||||
If the source passed in is already being used, this method will do nothing. | |||||
If the source is not null, its prepareToPlay() method will be called | |||||
before it starts being used for playback. | |||||
If there's another source currently playing, its releaseResources() method | |||||
will be called after it has been swapped for the new one. | |||||
@param newSource the new source to use - this will NOT be deleted | |||||
by this object when no longer needed, so it's the | |||||
caller's responsibility to manage it. | |||||
*/ | |||||
void setSource (AudioSource* newSource); | |||||
/** Returns the source that's playing. | |||||
May return 0 if there's no source. | |||||
*/ | |||||
AudioSource* getCurrentSource() const noexcept { return source; } | |||||
/** Sets a gain to apply to the audio data. | |||||
@see getGain | |||||
*/ | |||||
void setGain (float newGain) noexcept; | |||||
/** Returns the current gain. | |||||
@see setGain | |||||
*/ | |||||
float getGain() const noexcept { return gain; } | |||||
//============================================================================== | |||||
/** Implementation of the AudioIODeviceCallback method. */ | |||||
void audioDeviceIOCallback (const float** inputChannelData, | |||||
int totalNumInputChannels, | |||||
float** outputChannelData, | |||||
int totalNumOutputChannels, | |||||
int numSamples); | |||||
/** Implementation of the AudioIODeviceCallback method. */ | |||||
void audioDeviceAboutToStart (AudioIODevice* device); | |||||
/** Implementation of the AudioIODeviceCallback method. */ | |||||
void audioDeviceStopped(); | |||||
/** An alternative method for initialising the source without an AudioIODevice. */ | |||||
void prepareToPlay (double sampleRate, int blockSize); | |||||
private: | |||||
//============================================================================== | |||||
CriticalSection readLock; | |||||
AudioSource* source; | |||||
double sampleRate; | |||||
int bufferSize; | |||||
float* channels [128]; | |||||
float* outputChans [128]; | |||||
const float* inputChans [128]; | |||||
AudioSampleBuffer tempBuffer; | |||||
float lastGain, gain; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSourcePlayer) | |||||
}; | |||||
#endif // __JUCE_AUDIOSOURCEPLAYER_JUCEHEADER__ |
@@ -0,0 +1,302 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
AudioTransportSource::AudioTransportSource() | |||||
: source (nullptr), | |||||
resamplerSource (nullptr), | |||||
bufferingSource (nullptr), | |||||
positionableSource (nullptr), | |||||
masterSource (nullptr), | |||||
gain (1.0f), | |||||
lastGain (1.0f), | |||||
playing (false), | |||||
stopped (true), | |||||
sampleRate (44100.0), | |||||
sourceSampleRate (0.0), | |||||
blockSize (128), | |||||
readAheadBufferSize (0), | |||||
isPrepared (false), | |||||
inputStreamEOF (false) | |||||
{ | |||||
} | |||||
AudioTransportSource::~AudioTransportSource() | |||||
{ | |||||
setSource (nullptr); | |||||
releaseMasterResources(); | |||||
} | |||||
void AudioTransportSource::setSource (PositionableAudioSource* const newSource, | |||||
int readAheadBufferSize_, | |||||
TimeSliceThread* readAheadThread, | |||||
double sourceSampleRateToCorrectFor, | |||||
int maxNumChannels) | |||||
{ | |||||
if (source == newSource) | |||||
{ | |||||
if (source == nullptr) | |||||
return; | |||||
setSource (nullptr, 0, nullptr); // deselect and reselect to avoid releasing resources wrongly | |||||
} | |||||
readAheadBufferSize = readAheadBufferSize_; | |||||
sourceSampleRate = sourceSampleRateToCorrectFor; | |||||
ResamplingAudioSource* newResamplerSource = nullptr; | |||||
BufferingAudioSource* newBufferingSource = nullptr; | |||||
PositionableAudioSource* newPositionableSource = nullptr; | |||||
AudioSource* newMasterSource = nullptr; | |||||
ScopedPointer <ResamplingAudioSource> oldResamplerSource (resamplerSource); | |||||
ScopedPointer <BufferingAudioSource> oldBufferingSource (bufferingSource); | |||||
AudioSource* oldMasterSource = masterSource; | |||||
if (newSource != nullptr) | |||||
{ | |||||
newPositionableSource = newSource; | |||||
if (readAheadBufferSize_ > 0) | |||||
{ | |||||
// If you want to use a read-ahead buffer, you must also provide a TimeSliceThread | |||||
// for it to use! | |||||
jassert (readAheadThread != nullptr); | |||||
newPositionableSource = newBufferingSource | |||||
= new BufferingAudioSource (newPositionableSource, *readAheadThread, | |||||
false, readAheadBufferSize_, maxNumChannels); | |||||
} | |||||
newPositionableSource->setNextReadPosition (0); | |||||
if (sourceSampleRateToCorrectFor > 0) | |||||
newMasterSource = newResamplerSource | |||||
= new ResamplingAudioSource (newPositionableSource, false, maxNumChannels); | |||||
else | |||||
newMasterSource = newPositionableSource; | |||||
if (isPrepared) | |||||
{ | |||||
if (newResamplerSource != nullptr && sourceSampleRate > 0 && sampleRate > 0) | |||||
newResamplerSource->setResamplingRatio (sourceSampleRate / sampleRate); | |||||
newMasterSource->prepareToPlay (blockSize, sampleRate); | |||||
} | |||||
} | |||||
{ | |||||
const ScopedLock sl (callbackLock); | |||||
source = newSource; | |||||
resamplerSource = newResamplerSource; | |||||
bufferingSource = newBufferingSource; | |||||
masterSource = newMasterSource; | |||||
positionableSource = newPositionableSource; | |||||
playing = false; | |||||
} | |||||
if (oldMasterSource != nullptr) | |||||
oldMasterSource->releaseResources(); | |||||
} | |||||
void AudioTransportSource::start() | |||||
{ | |||||
if ((! playing) && masterSource != nullptr) | |||||
{ | |||||
{ | |||||
const ScopedLock sl (callbackLock); | |||||
playing = true; | |||||
stopped = false; | |||||
inputStreamEOF = false; | |||||
} | |||||
sendChangeMessage(); | |||||
} | |||||
} | |||||
void AudioTransportSource::stop() | |||||
{ | |||||
if (playing) | |||||
{ | |||||
{ | |||||
const ScopedLock sl (callbackLock); | |||||
playing = false; | |||||
} | |||||
int n = 500; | |||||
while (--n >= 0 && ! stopped) | |||||
Thread::sleep (2); | |||||
sendChangeMessage(); | |||||
} | |||||
} | |||||
void AudioTransportSource::setPosition (double newPosition) | |||||
{ | |||||
if (sampleRate > 0.0) | |||||
setNextReadPosition ((int64) (newPosition * sampleRate)); | |||||
} | |||||
double AudioTransportSource::getCurrentPosition() const | |||||
{ | |||||
if (sampleRate > 0.0) | |||||
return getNextReadPosition() / sampleRate; | |||||
return 0.0; | |||||
} | |||||
double AudioTransportSource::getLengthInSeconds() const | |||||
{ | |||||
return getTotalLength() / sampleRate; | |||||
} | |||||
void AudioTransportSource::setNextReadPosition (int64 newPosition) | |||||
{ | |||||
if (positionableSource != nullptr) | |||||
{ | |||||
if (sampleRate > 0 && sourceSampleRate > 0) | |||||
newPosition = (int64) (newPosition * sourceSampleRate / sampleRate); | |||||
positionableSource->setNextReadPosition (newPosition); | |||||
} | |||||
} | |||||
int64 AudioTransportSource::getNextReadPosition() const | |||||
{ | |||||
if (positionableSource != nullptr) | |||||
{ | |||||
const double ratio = (sampleRate > 0 && sourceSampleRate > 0) ? sampleRate / sourceSampleRate : 1.0; | |||||
return (int64) (positionableSource->getNextReadPosition() * ratio); | |||||
} | |||||
return 0; | |||||
} | |||||
int64 AudioTransportSource::getTotalLength() const | |||||
{ | |||||
const ScopedLock sl (callbackLock); | |||||
if (positionableSource != nullptr) | |||||
{ | |||||
const double ratio = (sampleRate > 0 && sourceSampleRate > 0) ? sampleRate / sourceSampleRate : 1.0; | |||||
return (int64) (positionableSource->getTotalLength() * ratio); | |||||
} | |||||
return 0; | |||||
} | |||||
bool AudioTransportSource::isLooping() const | |||||
{ | |||||
const ScopedLock sl (callbackLock); | |||||
return positionableSource != nullptr | |||||
&& positionableSource->isLooping(); | |||||
} | |||||
void AudioTransportSource::setGain (const float newGain) noexcept | |||||
{ | |||||
gain = newGain; | |||||
} | |||||
void AudioTransportSource::prepareToPlay (int samplesPerBlockExpected, | |||||
double sampleRate_) | |||||
{ | |||||
const ScopedLock sl (callbackLock); | |||||
sampleRate = sampleRate_; | |||||
blockSize = samplesPerBlockExpected; | |||||
if (masterSource != nullptr) | |||||
masterSource->prepareToPlay (samplesPerBlockExpected, sampleRate); | |||||
if (resamplerSource != nullptr && sourceSampleRate > 0) | |||||
resamplerSource->setResamplingRatio (sourceSampleRate / sampleRate); | |||||
isPrepared = true; | |||||
} | |||||
void AudioTransportSource::releaseMasterResources() | |||||
{ | |||||
const ScopedLock sl (callbackLock); | |||||
if (masterSource != nullptr) | |||||
masterSource->releaseResources(); | |||||
isPrepared = false; | |||||
} | |||||
void AudioTransportSource::releaseResources() | |||||
{ | |||||
releaseMasterResources(); | |||||
} | |||||
void AudioTransportSource::getNextAudioBlock (const AudioSourceChannelInfo& info) | |||||
{ | |||||
const ScopedLock sl (callbackLock); | |||||
inputStreamEOF = false; | |||||
if (masterSource != nullptr && ! stopped) | |||||
{ | |||||
masterSource->getNextAudioBlock (info); | |||||
if (! playing) | |||||
{ | |||||
// just stopped playing, so fade out the last block.. | |||||
for (int i = info.buffer->getNumChannels(); --i >= 0;) | |||||
info.buffer->applyGainRamp (i, info.startSample, jmin (256, info.numSamples), 1.0f, 0.0f); | |||||
if (info.numSamples > 256) | |||||
info.buffer->clear (info.startSample + 256, info.numSamples - 256); | |||||
} | |||||
if (positionableSource->getNextReadPosition() > positionableSource->getTotalLength() + 1 | |||||
&& ! positionableSource->isLooping()) | |||||
{ | |||||
playing = false; | |||||
inputStreamEOF = true; | |||||
sendChangeMessage(); | |||||
} | |||||
stopped = ! playing; | |||||
for (int i = info.buffer->getNumChannels(); --i >= 0;) | |||||
{ | |||||
info.buffer->applyGainRamp (i, info.startSample, info.numSamples, | |||||
lastGain, gain); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
info.clearActiveBufferRegion(); | |||||
stopped = true; | |||||
} | |||||
lastGain = gain; | |||||
} |
@@ -0,0 +1,187 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-11 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online 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.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef __JUCE_AUDIOTRANSPORTSOURCE_JUCEHEADER__ | |||||
#define __JUCE_AUDIOTRANSPORTSOURCE_JUCEHEADER__ | |||||
//============================================================================== | |||||
/** | |||||
An AudioSource that takes a PositionableAudioSource and allows it to be | |||||
played, stopped, started, etc. | |||||
This can also be told use a buffer and background thread to read ahead, and | |||||
if can correct for different sample-rates. | |||||
You may want to use one of these along with an AudioSourcePlayer and AudioIODevice | |||||
to control playback of an audio file. | |||||
@see AudioSource, AudioSourcePlayer | |||||
*/ | |||||
class JUCE_API AudioTransportSource : public PositionableAudioSource, | |||||
public ChangeBroadcaster | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an AudioTransportSource. | |||||
After creating one of these, use the setSource() method to select an input source. | |||||
*/ | |||||
AudioTransportSource(); | |||||
/** Destructor. */ | |||||
~AudioTransportSource(); | |||||
//============================================================================== | |||||
/** Sets the reader that is being used as the input source. | |||||
This will stop playback, reset the position to 0 and change to the new reader. | |||||
The source passed in will not be deleted by this object, so must be managed by | |||||
the caller. | |||||
@param newSource the new input source to use. This may be zero | |||||
@param readAheadBufferSize a size of buffer to use for reading ahead. If this | |||||
is zero, no reading ahead will be done; if it's | |||||
greater than zero, a BufferingAudioSource will be used | |||||
to do the reading-ahead. If you set a non-zero value here, | |||||
you'll also need to set the readAheadThread parameter. | |||||
@param readAheadThread if you set readAheadBufferSize to a non-zero value, then | |||||
you'll also need to supply this TimeSliceThread object for | |||||
the background reader to use. The thread object must not be | |||||
deleted while the AudioTransport source is still using it. | |||||
@param sourceSampleRateToCorrectFor if this is non-zero, it specifies the sample | |||||
rate of the source, and playback will be sample-rate | |||||
adjusted to maintain playback at the correct pitch. If | |||||
this is 0, no sample-rate adjustment will be performed | |||||
@param maxNumChannels the maximum number of channels that may need to be played | |||||
*/ | |||||
void setSource (PositionableAudioSource* newSource, | |||||
int readAheadBufferSize = 0, | |||||
TimeSliceThread* readAheadThread = nullptr, | |||||
double sourceSampleRateToCorrectFor = 0.0, | |||||
int maxNumChannels = 2); | |||||
//============================================================================== | |||||
/** Changes the current playback position in the source stream. | |||||
The next time the getNextAudioBlock() method is called, this | |||||
is the time from which it'll read data. | |||||
@see getPosition | |||||
*/ | |||||
void setPosition (double newPosition); | |||||
/** Returns the position that the next data block will be read from | |||||
This is a time in seconds. | |||||
*/ | |||||
double getCurrentPosition() const; | |||||
/** Returns the stream's length in seconds. */ | |||||
double getLengthInSeconds() const; | |||||
/** Returns true if the player has stopped because its input stream ran out of data. | |||||
*/ | |||||
bool hasStreamFinished() const noexcept { return inputStreamEOF; } | |||||
//============================================================================== | |||||
/** Starts playing (if a source has been selected). | |||||
If it starts playing, this will send a message to any ChangeListeners | |||||
that are registered with this object. | |||||
*/ | |||||
void start(); | |||||
/** Stops playing. | |||||
If it's actually playing, this will send a message to any ChangeListeners | |||||
that are registered with this object. | |||||
*/ | |||||
void stop(); | |||||
/** Returns true if it's currently playing. */ | |||||
bool isPlaying() const noexcept { return playing; } | |||||
//============================================================================== | |||||
/** Changes the gain to apply to the output. | |||||
@param newGain a factor by which to multiply the outgoing samples, | |||||
so 1.0 = 0dB, 0.5 = -6dB, 2.0 = 6dB, etc. | |||||
*/ | |||||
void setGain (float newGain) noexcept; | |||||
/** Returns the current gain setting. | |||||
@see setGain | |||||
*/ | |||||
float getGain() const noexcept { return gain; } | |||||
//============================================================================== | |||||
/** Implementation of the AudioSource method. */ | |||||
void prepareToPlay (int samplesPerBlockExpected, double sampleRate); | |||||
/** Implementation of the AudioSource method. */ | |||||
void releaseResources(); | |||||
/** Implementation of the AudioSource method. */ | |||||
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill); | |||||
//============================================================================== | |||||
/** Implements the PositionableAudioSource method. */ | |||||
void setNextReadPosition (int64 newPosition); | |||||
/** Implements the PositionableAudioSource method. */ | |||||
int64 getNextReadPosition() const; | |||||
/** Implements the PositionableAudioSource method. */ | |||||
int64 getTotalLength() const; | |||||
/** Implements the PositionableAudioSource method. */ | |||||
bool isLooping() const; | |||||
private: | |||||
//============================================================================== | |||||
PositionableAudioSource* source; | |||||
ResamplingAudioSource* resamplerSource; | |||||
BufferingAudioSource* bufferingSource; | |||||
PositionableAudioSource* positionableSource; | |||||
AudioSource* masterSource; | |||||
CriticalSection callbackLock; | |||||
float volatile gain, lastGain; | |||||
bool volatile playing, stopped; | |||||
double sampleRate, sourceSampleRate; | |||||
int blockSize, readAheadBufferSize; | |||||
bool isPrepared, inputStreamEOF; | |||||
void releaseMasterResources(); | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioTransportSource) | |||||
}; | |||||
#endif // __JUCE_AUDIOTRANSPORTSOURCE_JUCEHEADER__ |
@@ -0,0 +1,49 @@ | |||||
===================================================================== | |||||
I've incorporated FLAC directly into the Juce codebase because it makes | |||||
things much easier than having to make all your builds link correctly to | |||||
the appropriate libraries on every different platform. | |||||
I've made minimal changes to the FLAC code - just tweaked a few include paths | |||||
to make it build smoothly, added some headers to allow you to turn off FLAC | |||||
compilation, and commented-out a couple of unused bits of code. | |||||
===================================================================== | |||||
The following license is the BSD-style license that comes with the | |||||
Flac distribution, and which applies just to the files I've | |||||
included in this directory. For more info, and to get the rest of the | |||||
distribution, visit the Flac homepage: flac.sourceforge.net | |||||
===================================================================== | |||||
Copyright (C) 2000,2001,2002,2003,2004,2005,2006 Josh Coalson | |||||
Redistribution and use in source and binary forms, with or without | |||||
modification, are permitted provided that the following conditions | |||||
are met: | |||||
- Redistributions of source code must retain the above copyright | |||||
notice, this list of conditions and the following disclaimer. | |||||
- Redistributions in binary form must reproduce the above copyright | |||||
notice, this list of conditions and the following disclaimer in the | |||||
documentation and/or other materials provided with the distribution. | |||||
- Neither the name of the Xiph.org Foundation nor the names of its | |||||
contributors may be used to endorse or promote products derived from | |||||
this software without specific prior written permission. | |||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR | |||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@@ -0,0 +1,402 @@ | |||||
/* libFLAC - Free Lossless Audio Codec library | |||||
* Copyright (C) 2000,2001,2002,2003,2004,2005,2006,2007 Josh Coalson | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions | |||||
* are met: | |||||
* | |||||
* - Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* | |||||
* - Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* | |||||
* - Neither the name of the Xiph.org Foundation nor the names of its | |||||
* contributors may be used to endorse or promote products derived from | |||||
* this software without specific prior written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR | |||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
*/ | |||||
#ifndef FLAC__ALL_H | |||||
#define FLAC__ALL_H | |||||
#include "export.h" | |||||
#include "assert.h" | |||||
#include "callback.h" | |||||
#include "format.h" | |||||
#include "metadata.h" | |||||
#include "ordinals.h" | |||||
#include "stream_decoder.h" | |||||
#include "stream_encoder.h" | |||||
#ifdef _MSC_VER | |||||
/* OPT: an MSVC built-in would be better */ | |||||
static _inline FLAC__uint32 local_swap32_(FLAC__uint32 x) | |||||
{ | |||||
x = ((x<<8)&0xFF00FF00) | ((x>>8)&0x00FF00FF); | |||||
return (x>>16) | (x<<16); | |||||
} | |||||
#endif | |||||
#if defined(_MSC_VER) && defined(_X86_) | |||||
/* OPT: an MSVC built-in would be better */ | |||||
static void local_swap32_block_(FLAC__uint32 *start, FLAC__uint32 len) | |||||
{ | |||||
__asm { | |||||
mov edx, start | |||||
mov ecx, len | |||||
test ecx, ecx | |||||
loop1: | |||||
jz done1 | |||||
mov eax, [edx] | |||||
bswap eax | |||||
mov [edx], eax | |||||
add edx, 4 | |||||
dec ecx | |||||
jmp short loop1 | |||||
done1: | |||||
} | |||||
} | |||||
#endif | |||||
/** \mainpage | |||||
* | |||||
* \section intro Introduction | |||||
* | |||||
* This is the documentation for the FLAC C and C++ APIs. It is | |||||
* highly interconnected; this introduction should give you a top | |||||
* level idea of the structure and how to find the information you | |||||
* need. As a prerequisite you should have at least a basic | |||||
* knowledge of the FLAC format, documented | |||||
* <A HREF="../format.html">here</A>. | |||||
* | |||||
* \section c_api FLAC C API | |||||
* | |||||
* The FLAC C API is the interface to libFLAC, a set of structures | |||||
* describing the components of FLAC streams, and functions for | |||||
* encoding and decoding streams, as well as manipulating FLAC | |||||
* metadata in files. The public include files will be installed | |||||
* in your include area (for example /usr/include/FLAC/...). | |||||
* | |||||
* By writing a little code and linking against libFLAC, it is | |||||
* relatively easy to add FLAC support to another program. The | |||||
* library is licensed under <A HREF="../license.html">Xiph's BSD license</A>. | |||||
* Complete source code of libFLAC as well as the command-line | |||||
* encoder and plugins is available and is a useful source of | |||||
* examples. | |||||
* | |||||
* Aside from encoders and decoders, libFLAC provides a powerful | |||||
* metadata interface for manipulating metadata in FLAC files. It | |||||
* allows the user to add, delete, and modify FLAC metadata blocks | |||||
* and it can automatically take advantage of PADDING blocks to avoid | |||||
* rewriting the entire FLAC file when changing the size of the | |||||
* metadata. | |||||
* | |||||
* libFLAC usually only requires the standard C library and C math | |||||
* library. In particular, threading is not used so there is no | |||||
* dependency on a thread library. However, libFLAC does not use | |||||
* global variables and should be thread-safe. | |||||
* | |||||
* libFLAC also supports encoding to and decoding from Ogg FLAC. | |||||
* However the metadata editing interfaces currently have limited | |||||
* read-only support for Ogg FLAC files. | |||||
* | |||||
* \section cpp_api FLAC C++ API | |||||
* | |||||
* The FLAC C++ API is a set of classes that encapsulate the | |||||
* structures and functions in libFLAC. They provide slightly more | |||||
* functionality with respect to metadata but are otherwise | |||||
* equivalent. For the most part, they share the same usage as | |||||
* their counterparts in libFLAC, and the FLAC C API documentation | |||||
* can be used as a supplement. The public include files | |||||
* for the C++ API will be installed in your include area (for | |||||
* example /usr/include/FLAC++/...). | |||||
* | |||||
* libFLAC++ is also licensed under | |||||
* <A HREF="../license.html">Xiph's BSD license</A>. | |||||
* | |||||
* \section getting_started Getting Started | |||||
* | |||||
* A good starting point for learning the API is to browse through | |||||
* the <A HREF="modules.html">modules</A>. Modules are logical | |||||
* groupings of related functions or classes, which correspond roughly | |||||
* to header files or sections of header files. Each module includes a | |||||
* detailed description of the general usage of its functions or | |||||
* classes. | |||||
* | |||||
* From there you can go on to look at the documentation of | |||||
* individual functions. You can see different views of the individual | |||||
* functions through the links in top bar across this page. | |||||
* | |||||
* If you prefer a more hands-on approach, you can jump right to some | |||||
* <A HREF="../documentation_example_code.html">example code</A>. | |||||
* | |||||
* \section porting_guide Porting Guide | |||||
* | |||||
* Starting with FLAC 1.1.3 a \link porting Porting Guide \endlink | |||||
* has been introduced which gives detailed instructions on how to | |||||
* port your code to newer versions of FLAC. | |||||
* | |||||
* \section embedded_developers Embedded Developers | |||||
* | |||||
* libFLAC has grown larger over time as more functionality has been | |||||
* included, but much of it may be unnecessary for a particular embedded | |||||
* implementation. Unused parts may be pruned by some simple editing of | |||||
* src/libFLAC/Makefile.am. In general, the decoders, encoders, and | |||||
* metadata interface are all independent from each other. | |||||
* | |||||
* It is easiest to just describe the dependencies: | |||||
* | |||||
* - All modules depend on the \link flac_format Format \endlink module. | |||||
* - The decoders and encoders depend on the bitbuffer. | |||||
* - The decoder is independent of the encoder. The encoder uses the | |||||
* decoder because of the verify feature, but this can be removed if | |||||
* not needed. | |||||
* - Parts of the metadata interface require the stream decoder (but not | |||||
* the encoder). | |||||
* - Ogg support is selectable through the compile time macro | |||||
* \c FLAC__HAS_OGG. | |||||
* | |||||
* For example, if your application only requires the stream decoder, no | |||||
* encoder, and no metadata interface, you can remove the stream encoder | |||||
* and the metadata interface, which will greatly reduce the size of the | |||||
* library. | |||||
* | |||||
* Also, there are several places in the libFLAC code with comments marked | |||||
* with "OPT:" where a #define can be changed to enable code that might be | |||||
* faster on a specific platform. Experimenting with these can yield faster | |||||
* binaries. | |||||
*/ | |||||
/** \defgroup porting Porting Guide for New Versions | |||||
* | |||||
* This module describes differences in the library interfaces from | |||||
* version to version. It assists in the porting of code that uses | |||||
* the libraries to newer versions of FLAC. | |||||
* | |||||
* One simple facility for making porting easier that has been added | |||||
* in FLAC 1.1.3 is a set of \c #defines in \c export.h of each | |||||
* library's includes (e.g. \c include/FLAC/export.h). The | |||||
* \c #defines mirror the libraries' | |||||
* <A HREF="http://www.gnu.org/software/libtool/manual.html#Libtool-versioning">libtool version numbers</A>, | |||||
* e.g. in libFLAC there are \c FLAC_API_VERSION_CURRENT, | |||||
* \c FLAC_API_VERSION_REVISION, and \c FLAC_API_VERSION_AGE. | |||||
* These can be used to support multiple versions of an API during the | |||||
* transition phase, e.g. | |||||
* | |||||
* \code | |||||
* #if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 | |||||
* legacy code | |||||
* #else | |||||
* new code | |||||
* #endif | |||||
* \endcode | |||||
* | |||||
* The the source will work for multiple versions and the legacy code can | |||||
* easily be removed when the transition is complete. | |||||
* | |||||
* Another available symbol is FLAC_API_SUPPORTS_OGG_FLAC (defined in | |||||
* include/FLAC/export.h), which can be used to determine whether or not | |||||
* the library has been compiled with support for Ogg FLAC. This is | |||||
* simpler than trying to call an Ogg init function and catching the | |||||
* error. | |||||
*/ | |||||
/** \defgroup porting_1_1_2_to_1_1_3 Porting from FLAC 1.1.2 to 1.1.3 | |||||
* \ingroup porting | |||||
* | |||||
* \brief | |||||
* This module describes porting from FLAC 1.1.2 to FLAC 1.1.3. | |||||
* | |||||
* The main change between the APIs in 1.1.2 and 1.1.3 is that they have | |||||
* been simplified. First, libOggFLAC has been merged into libFLAC and | |||||
* libOggFLAC++ has been merged into libFLAC++. Second, both the three | |||||
* decoding layers and three encoding layers have been merged into a | |||||
* single stream decoder and stream encoder. That is, the functionality | |||||
* of FLAC__SeekableStreamDecoder and FLAC__FileDecoder has been merged | |||||
* into FLAC__StreamDecoder, and FLAC__SeekableStreamEncoder and | |||||
* FLAC__FileEncoder into FLAC__StreamEncoder. Only the | |||||
* FLAC__StreamDecoder and FLAC__StreamEncoder remain. What this means | |||||
* is there is now a single API that can be used to encode or decode | |||||
* streams to/from native FLAC or Ogg FLAC and the single API can work | |||||
* on both seekable and non-seekable streams. | |||||
* | |||||
* Instead of creating an encoder or decoder of a certain layer, now the | |||||
* client will always create a FLAC__StreamEncoder or | |||||
* FLAC__StreamDecoder. The old layers are now differentiated by the | |||||
* initialization function. For example, for the decoder, | |||||
* FLAC__stream_decoder_init() has been replaced by | |||||
* FLAC__stream_decoder_init_stream(). This init function takes | |||||
* callbacks for the I/O, and the seeking callbacks are optional. This | |||||
* allows the client to use the same object for seekable and | |||||
* non-seekable streams. For decoding a FLAC file directly, the client | |||||
* can use FLAC__stream_decoder_init_file() and pass just a filename | |||||
* and fewer callbacks; most of the other callbacks are supplied | |||||
* internally. For situations where fopen()ing by filename is not | |||||
* possible (e.g. Unicode filenames on Windows) the client can instead | |||||
* open the file itself and supply the FILE* to | |||||
* FLAC__stream_decoder_init_FILE(). The init functions now returns a | |||||
* FLAC__StreamDecoderInitStatus instead of FLAC__StreamDecoderState. | |||||
* Since the callbacks and client data are now passed to the init | |||||
* function, the FLAC__stream_decoder_set_*_callback() functions and | |||||
* FLAC__stream_decoder_set_client_data() are no longer needed. The | |||||
* rest of the calls to the decoder are the same as before. | |||||
* | |||||
* There are counterpart init functions for Ogg FLAC, e.g. | |||||
* FLAC__stream_decoder_init_ogg_stream(). All the rest of the calls | |||||
* and callbacks are the same as for native FLAC. | |||||
* | |||||
* As an example, in FLAC 1.1.2 a seekable stream decoder would have | |||||
* been set up like so: | |||||
* | |||||
* \code | |||||
* FLAC__SeekableStreamDecoder *decoder = FLAC__seekable_stream_decoder_new(); | |||||
* if(decoder == NULL) do_something; | |||||
* FLAC__seekable_stream_decoder_set_md5_checking(decoder, true); | |||||
* [... other settings ...] | |||||
* FLAC__seekable_stream_decoder_set_read_callback(decoder, my_read_callback); | |||||
* FLAC__seekable_stream_decoder_set_seek_callback(decoder, my_seek_callback); | |||||
* FLAC__seekable_stream_decoder_set_tell_callback(decoder, my_tell_callback); | |||||
* FLAC__seekable_stream_decoder_set_length_callback(decoder, my_length_callback); | |||||
* FLAC__seekable_stream_decoder_set_eof_callback(decoder, my_eof_callback); | |||||
* FLAC__seekable_stream_decoder_set_write_callback(decoder, my_write_callback); | |||||
* FLAC__seekable_stream_decoder_set_metadata_callback(decoder, my_metadata_callback); | |||||
* FLAC__seekable_stream_decoder_set_error_callback(decoder, my_error_callback); | |||||
* FLAC__seekable_stream_decoder_set_client_data(decoder, my_client_data); | |||||
* if(FLAC__seekable_stream_decoder_init(decoder) != FLAC__SEEKABLE_STREAM_DECODER_OK) do_something; | |||||
* \endcode | |||||
* | |||||
* In FLAC 1.1.3 it is like this: | |||||
* | |||||
* \code | |||||
* FLAC__StreamDecoder *decoder = FLAC__stream_decoder_new(); | |||||
* if(decoder == NULL) do_something; | |||||
* FLAC__stream_decoder_set_md5_checking(decoder, true); | |||||
* [... other settings ...] | |||||
* if(FLAC__stream_decoder_init_stream( | |||||
* decoder, | |||||
* my_read_callback, | |||||
* my_seek_callback, // or NULL | |||||
* my_tell_callback, // or NULL | |||||
* my_length_callback, // or NULL | |||||
* my_eof_callback, // or NULL | |||||
* my_write_callback, | |||||
* my_metadata_callback, // or NULL | |||||
* my_error_callback, | |||||
* my_client_data | |||||
* ) != FLAC__STREAM_DECODER_INIT_STATUS_OK) do_something; | |||||
* \endcode | |||||
* | |||||
* or you could do; | |||||
* | |||||
* \code | |||||
* [...] | |||||
* FILE *file = fopen("somefile.flac","rb"); | |||||
* if(file == NULL) do_somthing; | |||||
* if(FLAC__stream_decoder_init_FILE( | |||||
* decoder, | |||||
* file, | |||||
* my_write_callback, | |||||
* my_metadata_callback, // or NULL | |||||
* my_error_callback, | |||||
* my_client_data | |||||
* ) != FLAC__STREAM_DECODER_INIT_STATUS_OK) do_something; | |||||
* \endcode | |||||
* | |||||
* or just: | |||||
* | |||||
* \code | |||||
* [...] | |||||
* if(FLAC__stream_decoder_init_file( | |||||
* decoder, | |||||
* "somefile.flac", | |||||
* my_write_callback, | |||||
* my_metadata_callback, // or NULL | |||||
* my_error_callback, | |||||
* my_client_data | |||||
* ) != FLAC__STREAM_DECODER_INIT_STATUS_OK) do_something; | |||||
* \endcode | |||||
* | |||||
* Another small change to the decoder is in how it handles unparseable | |||||
* streams. Before, when the decoder found an unparseable stream | |||||
* (reserved for when the decoder encounters a stream from a future | |||||
* encoder that it can't parse), it changed the state to | |||||
* \c FLAC__STREAM_DECODER_UNPARSEABLE_STREAM. Now the decoder instead | |||||
* drops sync and calls the error callback with a new error code | |||||
* \c FLAC__STREAM_DECODER_ERROR_STATUS_UNPARSEABLE_STREAM. This is | |||||
* more robust. If your error callback does not discriminate on the the | |||||
* error state, your code does not need to be changed. | |||||
* | |||||
* The encoder now has a new setting: | |||||
* FLAC__stream_encoder_set_apodization(). This is for setting the | |||||
* method used to window the data before LPC analysis. You only need to | |||||
* add a call to this function if the default is not suitable. There | |||||
* are also two new convenience functions that may be useful: | |||||
* FLAC__metadata_object_cuesheet_calculate_cddb_id() and | |||||
* FLAC__metadata_get_cuesheet(). | |||||
* | |||||
* The \a bytes parameter to FLAC__StreamDecoderReadCallback, | |||||
* FLAC__StreamEncoderReadCallback, and FLAC__StreamEncoderWriteCallback | |||||
* is now \c size_t instead of \c unsigned. | |||||
*/ | |||||
/** \defgroup porting_1_1_3_to_1_1_4 Porting from FLAC 1.1.3 to 1.1.4 | |||||
* \ingroup porting | |||||
* | |||||
* \brief | |||||
* This module describes porting from FLAC 1.1.3 to FLAC 1.1.4. | |||||
* | |||||
* There were no changes to any of the interfaces from 1.1.3 to 1.1.4. | |||||
* There was a slight change in the implementation of | |||||
* FLAC__stream_encoder_set_metadata(); the function now makes a copy | |||||
* of the \a metadata array of pointers so the client no longer needs | |||||
* to maintain it after the call. The objects themselves that are | |||||
* pointed to by the array are still not copied though and must be | |||||
* maintained until the call to FLAC__stream_encoder_finish(). | |||||
*/ | |||||
/** \defgroup porting_1_1_4_to_1_2_0 Porting from FLAC 1.1.4 to 1.2.0 | |||||
* \ingroup porting | |||||
* | |||||
* \brief | |||||
* This module describes porting from FLAC 1.1.4 to FLAC 1.2.0. | |||||
* | |||||
* There were only very minor changes to the interfaces from 1.1.4 to 1.2.0. | |||||
* In libFLAC, \c FLAC__format_sample_rate_is_subset() was added. | |||||
* In libFLAC++, \c FLAC::Decoder::Stream::get_decode_position() was added. | |||||
* | |||||
* Finally, value of the constant \c FLAC__FRAME_HEADER_RESERVED_LEN | |||||
* has changed to reflect the conversion of one of the reserved bits | |||||
* into active use. It used to be \c 2 and now is \c 1. However the | |||||
* FLAC frame header length has not changed, so to skip the proper | |||||
* number of bits, use \c FLAC__FRAME_HEADER_RESERVED_LEN + | |||||
* \c FLAC__FRAME_HEADER_BLOCKING_STRATEGY_LEN | |||||
*/ | |||||
/** \defgroup flac FLAC C API | |||||
* | |||||
* The FLAC C API is the interface to libFLAC, a set of structures | |||||
* describing the components of FLAC streams, and functions for | |||||
* encoding and decoding streams, as well as manipulating FLAC | |||||
* metadata in files. | |||||
* | |||||
* You should start with the format components as all other modules | |||||
* are dependent on it. | |||||
*/ | |||||
#endif |
@@ -0,0 +1,212 @@ | |||||
/* alloc - Convenience routines for safely allocating memory | |||||
* Copyright (C) 2007 Josh Coalson | |||||
* | |||||
* This library is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 2.1 of the License, or (at your option) any later version. | |||||
* | |||||
* This library 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 | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public | |||||
* License along with this library; if not, write to the Free Software | |||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |||||
*/ | |||||
#ifndef FLAC__SHARE__ALLOC_H | |||||
#define FLAC__SHARE__ALLOC_H | |||||
#if HAVE_CONFIG_H | |||||
# include <config.h> | |||||
#endif | |||||
/* WATCHOUT: for c++ you may have to #define __STDC_LIMIT_MACROS 1 real early | |||||
* before #including this file, otherwise SIZE_MAX might not be defined | |||||
*/ | |||||
#include <limits.h> /* for SIZE_MAX */ | |||||
#if !defined _MSC_VER && !defined __MINGW32__ && !defined __EMX__ | |||||
#include <stdint.h> /* for SIZE_MAX in case limits.h didn't get it */ | |||||
#endif | |||||
#include <stdlib.h> /* for size_t, malloc(), etc */ | |||||
#ifndef SIZE_MAX | |||||
# ifndef SIZE_T_MAX | |||||
# ifdef _MSC_VER | |||||
# define SIZE_T_MAX UINT_MAX | |||||
# else | |||||
# error | |||||
# endif | |||||
# endif | |||||
# define SIZE_MAX SIZE_T_MAX | |||||
#endif | |||||
#ifndef FLaC__INLINE | |||||
#define FLaC__INLINE | |||||
#endif | |||||
/* avoid malloc()ing 0 bytes, see: | |||||
* https://www.securecoding.cert.org/confluence/display/seccode/MEM04-A.+Do+not+make+assumptions+about+the+result+of+allocating+0+bytes?focusedCommentId=5407003 | |||||
*/ | |||||
static FLaC__INLINE void *safe_malloc_(size_t size) | |||||
{ | |||||
/* malloc(0) is undefined; FLAC src convention is to always allocate */ | |||||
if(!size) | |||||
size++; | |||||
return malloc(size); | |||||
} | |||||
static FLaC__INLINE void *safe_calloc_(size_t nmemb, size_t size) | |||||
{ | |||||
if(!nmemb || !size) | |||||
return malloc(1); /* malloc(0) is undefined; FLAC src convention is to always allocate */ | |||||
return calloc(nmemb, size); | |||||
} | |||||
/*@@@@ there's probably a better way to prevent overflows when allocating untrusted sums but this works for now */ | |||||
static FLaC__INLINE void *safe_malloc_add_2op_(size_t size1, size_t size2) | |||||
{ | |||||
size2 += size1; | |||||
if(size2 < size1) | |||||
return 0; | |||||
return safe_malloc_(size2); | |||||
} | |||||
static FLaC__INLINE void *safe_malloc_add_3op_(size_t size1, size_t size2, size_t size3) | |||||
{ | |||||
size2 += size1; | |||||
if(size2 < size1) | |||||
return 0; | |||||
size3 += size2; | |||||
if(size3 < size2) | |||||
return 0; | |||||
return safe_malloc_(size3); | |||||
} | |||||
static FLaC__INLINE void *safe_malloc_add_4op_(size_t size1, size_t size2, size_t size3, size_t size4) | |||||
{ | |||||
size2 += size1; | |||||
if(size2 < size1) | |||||
return 0; | |||||
size3 += size2; | |||||
if(size3 < size2) | |||||
return 0; | |||||
size4 += size3; | |||||
if(size4 < size3) | |||||
return 0; | |||||
return safe_malloc_(size4); | |||||
} | |||||
static FLaC__INLINE void *safe_malloc_mul_2op_(size_t size1, size_t size2) | |||||
#if 0 | |||||
needs support for cases where sizeof(size_t) != 4 | |||||
{ | |||||
/* could be faster #ifdef'ing off SIZEOF_SIZE_T */ | |||||
if(sizeof(size_t) == 4) { | |||||
if ((double)size1 * (double)size2 < 4294967296.0) | |||||
return malloc(size1*size2); | |||||
} | |||||
return 0; | |||||
} | |||||
#else | |||||
/* better? */ | |||||
{ | |||||
if(!size1 || !size2) | |||||
return malloc(1); /* malloc(0) is undefined; FLAC src convention is to always allocate */ | |||||
if(size1 > SIZE_MAX / size2) | |||||
return 0; | |||||
return malloc(size1*size2); | |||||
} | |||||
#endif | |||||
static FLaC__INLINE void *safe_malloc_mul_3op_(size_t size1, size_t size2, size_t size3) | |||||
{ | |||||
if(!size1 || !size2 || !size3) | |||||
return malloc(1); /* malloc(0) is undefined; FLAC src convention is to always allocate */ | |||||
if(size1 > SIZE_MAX / size2) | |||||
return 0; | |||||
size1 *= size2; | |||||
if(size1 > SIZE_MAX / size3) | |||||
return 0; | |||||
return malloc(size1*size3); | |||||
} | |||||
/* size1*size2 + size3 */ | |||||
static FLaC__INLINE void *safe_malloc_mul2add_(size_t size1, size_t size2, size_t size3) | |||||
{ | |||||
if(!size1 || !size2) | |||||
return safe_malloc_(size3); | |||||
if(size1 > SIZE_MAX / size2) | |||||
return 0; | |||||
return safe_malloc_add_2op_(size1*size2, size3); | |||||
} | |||||
/* size1 * (size2 + size3) */ | |||||
static FLaC__INLINE void *safe_malloc_muladd2_(size_t size1, size_t size2, size_t size3) | |||||
{ | |||||
if(!size1 || (!size2 && !size3)) | |||||
return malloc(1); /* malloc(0) is undefined; FLAC src convention is to always allocate */ | |||||
size2 += size3; | |||||
if(size2 < size3) | |||||
return 0; | |||||
return safe_malloc_mul_2op_(size1, size2); | |||||
} | |||||
static FLaC__INLINE void *safe_realloc_add_2op_(void *ptr, size_t size1, size_t size2) | |||||
{ | |||||
size2 += size1; | |||||
if(size2 < size1) | |||||
return 0; | |||||
return realloc(ptr, size2); | |||||
} | |||||
static FLaC__INLINE void *safe_realloc_add_3op_(void *ptr, size_t size1, size_t size2, size_t size3) | |||||
{ | |||||
size2 += size1; | |||||
if(size2 < size1) | |||||
return 0; | |||||
size3 += size2; | |||||
if(size3 < size2) | |||||
return 0; | |||||
return realloc(ptr, size3); | |||||
} | |||||
static FLaC__INLINE void *safe_realloc_add_4op_(void *ptr, size_t size1, size_t size2, size_t size3, size_t size4) | |||||
{ | |||||
size2 += size1; | |||||
if(size2 < size1) | |||||
return 0; | |||||
size3 += size2; | |||||
if(size3 < size2) | |||||
return 0; | |||||
size4 += size3; | |||||
if(size4 < size3) | |||||
return 0; | |||||
return realloc(ptr, size4); | |||||
} | |||||
static FLaC__INLINE void *safe_realloc_mul_2op_(void *ptr, size_t size1, size_t size2) | |||||
{ | |||||
if(!size1 || !size2) | |||||
return realloc(ptr, 0); /* preserve POSIX realloc(ptr, 0) semantics */ | |||||
if(size1 > SIZE_MAX / size2) | |||||
return 0; | |||||
return realloc(ptr, size1*size2); | |||||
} | |||||
/* size1 * (size2 + size3) */ | |||||
static FLaC__INLINE void *safe_realloc_muladd2_(void *ptr, size_t size1, size_t size2, size_t size3) | |||||
{ | |||||
if(!size1 || (!size2 && !size3)) | |||||
return realloc(ptr, 0); /* preserve POSIX realloc(ptr, 0) semantics */ | |||||
size2 += size3; | |||||
if(size2 < size3) | |||||
return 0; | |||||
return safe_realloc_mul_2op_(ptr, size1, size2); | |||||
} | |||||
#endif |
@@ -0,0 +1,45 @@ | |||||
/* libFLAC - Free Lossless Audio Codec library | |||||
* Copyright (C) 2001,2002,2003,2004,2005,2006,2007 Josh Coalson | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions | |||||
* are met: | |||||
* | |||||
* - Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* | |||||
* - Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* | |||||
* - Neither the name of the Xiph.org Foundation nor the names of its | |||||
* contributors may be used to endorse or promote products derived from | |||||
* this software without specific prior written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR | |||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
*/ | |||||
#ifndef FLAC__ASSERT_H | |||||
#define FLAC__ASSERT_H | |||||
/* we need this since some compilers (like MSVC) leave assert()s on release code (and we don't want to use their ASSERT) */ | |||||
#ifdef DEBUG | |||||
#include <assert.h> | |||||
#define FLAC__ASSERT(x) assert(x) | |||||
#define FLAC__ASSERT_DECLARATION(x) x | |||||
#else | |||||
#define FLAC__ASSERT(x) | |||||
#define FLAC__ASSERT_DECLARATION(x) | |||||
#endif | |||||
#endif |
@@ -0,0 +1,184 @@ | |||||
/* libFLAC - Free Lossless Audio Codec library | |||||
* Copyright (C) 2004,2005,2006,2007 Josh Coalson | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions | |||||
* are met: | |||||
* | |||||
* - Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* | |||||
* - Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* | |||||
* - Neither the name of the Xiph.org Foundation nor the names of its | |||||
* contributors may be used to endorse or promote products derived from | |||||
* this software without specific prior written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR | |||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
*/ | |||||
#ifndef FLAC__CALLBACK_H | |||||
#define FLAC__CALLBACK_H | |||||
#include "ordinals.h" | |||||
#include <stdlib.h> /* for size_t */ | |||||
/** \file include/FLAC/callback.h | |||||
* | |||||
* \brief | |||||
* This module defines the structures for describing I/O callbacks | |||||
* to the other FLAC interfaces. | |||||
* | |||||
* See the detailed documentation for callbacks in the | |||||
* \link flac_callbacks callbacks \endlink module. | |||||
*/ | |||||
/** \defgroup flac_callbacks FLAC/callback.h: I/O callback structures | |||||
* \ingroup flac | |||||
* | |||||
* \brief | |||||
* This module defines the structures for describing I/O callbacks | |||||
* to the other FLAC interfaces. | |||||
* | |||||
* The purpose of the I/O callback functions is to create a common way | |||||
* for the metadata interfaces to handle I/O. | |||||
* | |||||
* Originally the metadata interfaces required filenames as the way of | |||||
* specifying FLAC files to operate on. This is problematic in some | |||||
* environments so there is an additional option to specify a set of | |||||
* callbacks for doing I/O on the FLAC file, instead of the filename. | |||||
* | |||||
* In addition to the callbacks, a FLAC__IOHandle type is defined as an | |||||
* opaque structure for a data source. | |||||
* | |||||
* The callback function prototypes are similar (but not identical) to the | |||||
* stdio functions fread, fwrite, fseek, ftell, feof, and fclose. If you use | |||||
* stdio streams to implement the callbacks, you can pass fread, fwrite, and | |||||
* fclose anywhere a FLAC__IOCallback_Read, FLAC__IOCallback_Write, or | |||||
* FLAC__IOCallback_Close is required, and a FILE* anywhere a FLAC__IOHandle | |||||
* is required. \warning You generally CANNOT directly use fseek or ftell | |||||
* for FLAC__IOCallback_Seek or FLAC__IOCallback_Tell since on most systems | |||||
* these use 32-bit offsets and FLAC requires 64-bit offsets to deal with | |||||
* large files. You will have to find an equivalent function (e.g. ftello), | |||||
* or write a wrapper. The same is true for feof() since this is usually | |||||
* implemented as a macro, not as a function whose address can be taken. | |||||
* | |||||
* \{ | |||||
*/ | |||||
#ifdef __cplusplus | |||||
extern "C" { | |||||
#endif | |||||
/** This is the opaque handle type used by the callbacks. Typically | |||||
* this is a \c FILE* or address of a file descriptor. | |||||
*/ | |||||
typedef void* FLAC__IOHandle; | |||||
/** Signature for the read callback. | |||||
* The signature and semantics match POSIX fread() implementations | |||||
* and can generally be used interchangeably. | |||||
* | |||||
* \param ptr The address of the read buffer. | |||||
* \param size The size of the records to be read. | |||||
* \param nmemb The number of records to be read. | |||||
* \param handle The handle to the data source. | |||||
* \retval size_t | |||||
* The number of records read. | |||||
*/ | |||||
typedef size_t (*FLAC__IOCallback_Read) (void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle); | |||||
/** Signature for the write callback. | |||||
* The signature and semantics match POSIX fwrite() implementations | |||||
* and can generally be used interchangeably. | |||||
* | |||||
* \param ptr The address of the write buffer. | |||||
* \param size The size of the records to be written. | |||||
* \param nmemb The number of records to be written. | |||||
* \param handle The handle to the data source. | |||||
* \retval size_t | |||||
* The number of records written. | |||||
*/ | |||||
typedef size_t (*FLAC__IOCallback_Write) (const void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle); | |||||
/** Signature for the seek callback. | |||||
* The signature and semantics mostly match POSIX fseek() WITH ONE IMPORTANT | |||||
* EXCEPTION: the offset is a 64-bit type whereas fseek() is generally 'long' | |||||
* and 32-bits wide. | |||||
* | |||||
* \param handle The handle to the data source. | |||||
* \param offset The new position, relative to \a whence | |||||
* \param whence \c SEEK_SET, \c SEEK_CUR, or \c SEEK_END | |||||
* \retval int | |||||
* \c 0 on success, \c -1 on error. | |||||
*/ | |||||
typedef int (*FLAC__IOCallback_Seek) (FLAC__IOHandle handle, FLAC__int64 offset, int whence); | |||||
/** Signature for the tell callback. | |||||
* The signature and semantics mostly match POSIX ftell() WITH ONE IMPORTANT | |||||
* EXCEPTION: the offset is a 64-bit type whereas ftell() is generally 'long' | |||||
* and 32-bits wide. | |||||
* | |||||
* \param handle The handle to the data source. | |||||
* \retval FLAC__int64 | |||||
* The current position on success, \c -1 on error. | |||||
*/ | |||||
typedef FLAC__int64 (*FLAC__IOCallback_Tell) (FLAC__IOHandle handle); | |||||
/** Signature for the EOF callback. | |||||
* The signature and semantics mostly match POSIX feof() but WATCHOUT: | |||||
* on many systems, feof() is a macro, so in this case a wrapper function | |||||
* must be provided instead. | |||||
* | |||||
* \param handle The handle to the data source. | |||||
* \retval int | |||||
* \c 0 if not at end of file, nonzero if at end of file. | |||||
*/ | |||||
typedef int (*FLAC__IOCallback_Eof) (FLAC__IOHandle handle); | |||||
/** Signature for the close callback. | |||||
* The signature and semantics match POSIX fclose() implementations | |||||
* and can generally be used interchangeably. | |||||
* | |||||
* \param handle The handle to the data source. | |||||
* \retval int | |||||
* \c 0 on success, \c EOF on error. | |||||
*/ | |||||
typedef int (*FLAC__IOCallback_Close) (FLAC__IOHandle handle); | |||||
/** A structure for holding a set of callbacks. | |||||
* Each FLAC interface that requires a FLAC__IOCallbacks structure will | |||||
* describe which of the callbacks are required. The ones that are not | |||||
* required may be set to NULL. | |||||
* | |||||
* If the seek requirement for an interface is optional, you can signify that | |||||
* a data sorce is not seekable by setting the \a seek field to \c NULL. | |||||
*/ | |||||
typedef struct { | |||||
FLAC__IOCallback_Read read; | |||||
FLAC__IOCallback_Write write; | |||||
FLAC__IOCallback_Seek seek; | |||||
FLAC__IOCallback_Tell tell; | |||||
FLAC__IOCallback_Eof eof; | |||||
FLAC__IOCallback_Close close; | |||||
} FLAC__IOCallbacks; | |||||
/* \} */ | |||||
#ifdef __cplusplus | |||||
} | |||||
#endif | |||||
#endif |
@@ -0,0 +1,91 @@ | |||||
/* libFLAC - Free Lossless Audio Codec library | |||||
* Copyright (C) 2000,2001,2002,2003,2004,2005,2006,2007 Josh Coalson | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions | |||||
* are met: | |||||
* | |||||
* - Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* | |||||
* - Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* | |||||
* - Neither the name of the Xiph.org Foundation nor the names of its | |||||
* contributors may be used to endorse or promote products derived from | |||||
* this software without specific prior written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR | |||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
*/ | |||||
#ifndef FLAC__EXPORT_H | |||||
#define FLAC__EXPORT_H | |||||
/** \file include/FLAC/export.h | |||||
* | |||||
* \brief | |||||
* This module contains #defines and symbols for exporting function | |||||
* calls, and providing version information and compiled-in features. | |||||
* | |||||
* See the \link flac_export export \endlink module. | |||||
*/ | |||||
/** \defgroup flac_export FLAC/export.h: export symbols | |||||
* \ingroup flac | |||||
* | |||||
* \brief | |||||
* This module contains #defines and symbols for exporting function | |||||
* calls, and providing version information and compiled-in features. | |||||
* | |||||
* If you are compiling with MSVC and will link to the static library | |||||
* (libFLAC.lib) you should define FLAC__NO_DLL in your project to | |||||
* make sure the symbols are exported properly. | |||||
* | |||||
* \{ | |||||
*/ | |||||
#if defined(FLAC__NO_DLL) || !defined(_MSC_VER) | |||||
#define FLAC_API | |||||
#else | |||||
#ifdef FLAC_API_EXPORTS | |||||
#define FLAC_API _declspec(dllexport) | |||||
#else | |||||
#define FLAC_API _declspec(dllimport) | |||||
#endif | |||||
#endif | |||||
/** These #defines will mirror the libtool-based library version number, see | |||||
* http://www.gnu.org/software/libtool/manual.html#Libtool-versioning | |||||
*/ | |||||
#define FLAC_API_VERSION_CURRENT 10 | |||||
#define FLAC_API_VERSION_REVISION 0 /**< see above */ | |||||
#define FLAC_API_VERSION_AGE 2 /**< see above */ | |||||
#ifdef __cplusplus | |||||
extern "C" { | |||||
#endif | |||||
/** \c 1 if the library has been compiled with support for Ogg FLAC, else \c 0. */ | |||||
extern FLAC_API int FLAC_API_SUPPORTS_OGG_FLAC; | |||||
#ifdef __cplusplus | |||||
} | |||||
#endif | |||||
/* \} */ | |||||
#endif |
@@ -0,0 +1,149 @@ | |||||
/* libFLAC - Free Lossless Audio Codec library | |||||
* Copyright (C) 2001,2002,2003,2004,2005,2006,2007 Josh Coalson | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions | |||||
* are met: | |||||
* | |||||
* - Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* | |||||
* - Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* | |||||
* - Neither the name of the Xiph.org Foundation nor the names of its | |||||
* contributors may be used to endorse or promote products derived from | |||||
* this software without specific prior written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR | |||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
*/ | |||||
#if HAVE_CONFIG_H | |||||
# include <config.h> | |||||
#endif | |||||
#include "include/private/bitmath.h" | |||||
#include "../assert.h" | |||||
/* An example of what FLAC__bitmath_ilog2() computes: | |||||
* | |||||
* ilog2( 0) = assertion failure | |||||
* ilog2( 1) = 0 | |||||
* ilog2( 2) = 1 | |||||
* ilog2( 3) = 1 | |||||
* ilog2( 4) = 2 | |||||
* ilog2( 5) = 2 | |||||
* ilog2( 6) = 2 | |||||
* ilog2( 7) = 2 | |||||
* ilog2( 8) = 3 | |||||
* ilog2( 9) = 3 | |||||
* ilog2(10) = 3 | |||||
* ilog2(11) = 3 | |||||
* ilog2(12) = 3 | |||||
* ilog2(13) = 3 | |||||
* ilog2(14) = 3 | |||||
* ilog2(15) = 3 | |||||
* ilog2(16) = 4 | |||||
* ilog2(17) = 4 | |||||
* ilog2(18) = 4 | |||||
*/ | |||||
unsigned FLAC__bitmath_ilog2(FLAC__uint32 v) | |||||
{ | |||||
unsigned l = 0; | |||||
FLAC__ASSERT(v > 0); | |||||
while(v >>= 1) | |||||
l++; | |||||
return l; | |||||
} | |||||
unsigned FLAC__bitmath_ilog2_wide(FLAC__uint64 v) | |||||
{ | |||||
unsigned l = 0; | |||||
FLAC__ASSERT(v > 0); | |||||
while(v >>= 1) | |||||
l++; | |||||
return l; | |||||
} | |||||
/* An example of what FLAC__bitmath_silog2() computes: | |||||
* | |||||
* silog2(-10) = 5 | |||||
* silog2(- 9) = 5 | |||||
* silog2(- 8) = 4 | |||||
* silog2(- 7) = 4 | |||||
* silog2(- 6) = 4 | |||||
* silog2(- 5) = 4 | |||||
* silog2(- 4) = 3 | |||||
* silog2(- 3) = 3 | |||||
* silog2(- 2) = 2 | |||||
* silog2(- 1) = 2 | |||||
* silog2( 0) = 0 | |||||
* silog2( 1) = 2 | |||||
* silog2( 2) = 3 | |||||
* silog2( 3) = 3 | |||||
* silog2( 4) = 4 | |||||
* silog2( 5) = 4 | |||||
* silog2( 6) = 4 | |||||
* silog2( 7) = 4 | |||||
* silog2( 8) = 5 | |||||
* silog2( 9) = 5 | |||||
* silog2( 10) = 5 | |||||
*/ | |||||
unsigned FLAC__bitmath_silog2(int v) | |||||
{ | |||||
while(1) { | |||||
if(v == 0) { | |||||
return 0; | |||||
} | |||||
else if(v > 0) { | |||||
unsigned l = 0; | |||||
while(v) { | |||||
l++; | |||||
v >>= 1; | |||||
} | |||||
return l+1; | |||||
} | |||||
else if(v == -1) { | |||||
return 2; | |||||
} | |||||
else { | |||||
v++; | |||||
v = -v; | |||||
} | |||||
} | |||||
} | |||||
unsigned FLAC__bitmath_silog2_wide(FLAC__int64 v) | |||||
{ | |||||
while(1) { | |||||
if(v == 0) { | |||||
return 0; | |||||
} | |||||
else if(v > 0) { | |||||
unsigned l = 0; | |||||
while(v) { | |||||
l++; | |||||
v >>= 1; | |||||
} | |||||
return l+1; | |||||
} | |||||
else if(v == -1) { | |||||
return 2; | |||||
} | |||||
else { | |||||
v++; | |||||
v = -v; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,880 @@ | |||||
/* libFLAC - Free Lossless Audio Codec library | |||||
* Copyright (C) 2000,2001,2002,2003,2004,2005,2006,2007 Josh Coalson | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions | |||||
* are met: | |||||
* | |||||
* - Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* | |||||
* - Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* | |||||
* - Neither the name of the Xiph.org Foundation nor the names of its | |||||
* contributors may be used to endorse or promote products derived from | |||||
* this software without specific prior written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR | |||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
*/ | |||||
#if HAVE_CONFIG_H | |||||
# include <config.h> | |||||
#endif | |||||
#include <stdlib.h> /* for malloc() */ | |||||
#include <string.h> /* for memcpy(), memset() */ | |||||
#ifdef _MSC_VER | |||||
#include <winsock.h> /* for ntohl() */ | |||||
#elif defined FLAC__SYS_DARWIN | |||||
#include <machine/endian.h> /* for ntohl() */ | |||||
#elif defined __MINGW32__ | |||||
#include <winsock.h> /* for ntohl() */ | |||||
#else | |||||
#include <netinet/in.h> /* for ntohl() */ | |||||
#endif | |||||
#if 0 /* UNUSED */ | |||||
#include "include/private/bitmath.h" | |||||
#endif | |||||
#include "include/private/bitwriter.h" | |||||
#include "include/private/crc.h" | |||||
#include "../assert.h" | |||||
#include "../alloc.h" | |||||
/* Things should be fastest when this matches the machine word size */ | |||||
/* WATCHOUT: if you change this you must also change the following #defines down to SWAP_BE_WORD_TO_HOST below to match */ | |||||
/* WATCHOUT: there are a few places where the code will not work unless bwword is >= 32 bits wide */ | |||||
typedef FLAC__uint32 bwword; | |||||
#define FLAC__BYTES_PER_WORD 4 | |||||
#define FLAC__BITS_PER_WORD 32 | |||||
#define FLAC__WORD_ALL_ONES ((FLAC__uint32)0xffffffff) | |||||
/* SWAP_BE_WORD_TO_HOST swaps bytes in a bwword (which is always big-endian) if necessary to match host byte order */ | |||||
#if WORDS_BIGENDIAN | |||||
#define SWAP_BE_WORD_TO_HOST(x) (x) | |||||
#else | |||||
#ifdef _MSC_VER | |||||
#define SWAP_BE_WORD_TO_HOST(x) local_swap32_(x) | |||||
#else | |||||
#define SWAP_BE_WORD_TO_HOST(x) ntohl(x) | |||||
#endif | |||||
#endif | |||||
/* | |||||
* The default capacity here doesn't matter too much. The buffer always grows | |||||
* to hold whatever is written to it. Usually the encoder will stop adding at | |||||
* a frame or metadata block, then write that out and clear the buffer for the | |||||
* next one. | |||||
*/ | |||||
static const unsigned FLAC__BITWRITER_DEFAULT_CAPACITY = 32768u / sizeof(bwword); /* size in words */ | |||||
/* When growing, increment 4K at a time */ | |||||
static const unsigned FLAC__BITWRITER_DEFAULT_INCREMENT = 4096u / sizeof(bwword); /* size in words */ | |||||
#define FLAC__WORDS_TO_BITS(words) ((words) * FLAC__BITS_PER_WORD) | |||||
#define FLAC__TOTAL_BITS(bw) (FLAC__WORDS_TO_BITS((bw)->words) + (bw)->bits) | |||||
#ifdef min | |||||
#undef min | |||||
#endif | |||||
#define min(x,y) ((x)<(y)?(x):(y)) | |||||
/* adjust for compilers that can't understand using LLU suffix for uint64_t literals */ | |||||
#ifdef _MSC_VER | |||||
#define FLAC__U64L(x) x | |||||
#else | |||||
#define FLAC__U64L(x) x##LLU | |||||
#endif | |||||
#ifndef FLaC__INLINE | |||||
#define FLaC__INLINE | |||||
#endif | |||||
struct FLAC__BitWriter { | |||||
bwword *buffer; | |||||
bwword accum; /* accumulator; bits are right-justified; when full, accum is appended to buffer */ | |||||
unsigned capacity; /* capacity of buffer in words */ | |||||
unsigned words; /* # of complete words in buffer */ | |||||
unsigned bits; /* # of used bits in accum */ | |||||
}; | |||||
/* * WATCHOUT: The current implementation only grows the buffer. */ | |||||
static FLAC__bool bitwriter_grow_(FLAC__BitWriter *bw, unsigned bits_to_add) | |||||
{ | |||||
unsigned new_capacity; | |||||
bwword *new_buffer; | |||||
FLAC__ASSERT(0 != bw); | |||||
FLAC__ASSERT(0 != bw->buffer); | |||||
/* calculate total words needed to store 'bits_to_add' additional bits */ | |||||
new_capacity = bw->words + ((bw->bits + bits_to_add + FLAC__BITS_PER_WORD - 1) / FLAC__BITS_PER_WORD); | |||||
/* it's possible (due to pessimism in the growth estimation that | |||||
* leads to this call) that we don't actually need to grow | |||||
*/ | |||||
if(bw->capacity >= new_capacity) | |||||
return true; | |||||
/* round up capacity increase to the nearest FLAC__BITWRITER_DEFAULT_INCREMENT */ | |||||
if((new_capacity - bw->capacity) % FLAC__BITWRITER_DEFAULT_INCREMENT) | |||||
new_capacity += FLAC__BITWRITER_DEFAULT_INCREMENT - ((new_capacity - bw->capacity) % FLAC__BITWRITER_DEFAULT_INCREMENT); | |||||
/* make sure we got everything right */ | |||||
FLAC__ASSERT(0 == (new_capacity - bw->capacity) % FLAC__BITWRITER_DEFAULT_INCREMENT); | |||||
FLAC__ASSERT(new_capacity > bw->capacity); | |||||
FLAC__ASSERT(new_capacity >= bw->words + ((bw->bits + bits_to_add + FLAC__BITS_PER_WORD - 1) / FLAC__BITS_PER_WORD)); | |||||
new_buffer = (bwword*)safe_realloc_mul_2op_(bw->buffer, sizeof(bwword), /*times*/new_capacity); | |||||
if(new_buffer == 0) | |||||
return false; | |||||
bw->buffer = new_buffer; | |||||
bw->capacity = new_capacity; | |||||
return true; | |||||
} | |||||
/*********************************************************************** | |||||
* | |||||
* Class constructor/destructor | |||||
* | |||||
***********************************************************************/ | |||||
FLAC__BitWriter *FLAC__bitwriter_new(void) | |||||
{ | |||||
FLAC__BitWriter *bw = (FLAC__BitWriter*)calloc(1, sizeof(FLAC__BitWriter)); | |||||
/* note that calloc() sets all members to 0 for us */ | |||||
return bw; | |||||
} | |||||
void FLAC__bitwriter_delete(FLAC__BitWriter *bw) | |||||
{ | |||||
FLAC__ASSERT(0 != bw); | |||||
FLAC__bitwriter_free(bw); | |||||
free(bw); | |||||
} | |||||
/*********************************************************************** | |||||
* | |||||
* Public class methods | |||||
* | |||||
***********************************************************************/ | |||||
FLAC__bool FLAC__bitwriter_init(FLAC__BitWriter *bw) | |||||
{ | |||||
FLAC__ASSERT(0 != bw); | |||||
bw->words = bw->bits = 0; | |||||
bw->capacity = FLAC__BITWRITER_DEFAULT_CAPACITY; | |||||
bw->buffer = (bwword*)malloc(sizeof(bwword) * bw->capacity); | |||||
if(bw->buffer == 0) | |||||
return false; | |||||
return true; | |||||
} | |||||
void FLAC__bitwriter_free(FLAC__BitWriter *bw) | |||||
{ | |||||
FLAC__ASSERT(0 != bw); | |||||
if(0 != bw->buffer) | |||||
free(bw->buffer); | |||||
bw->buffer = 0; | |||||
bw->capacity = 0; | |||||
bw->words = bw->bits = 0; | |||||
} | |||||
void FLAC__bitwriter_clear(FLAC__BitWriter *bw) | |||||
{ | |||||
bw->words = bw->bits = 0; | |||||
} | |||||
void FLAC__bitwriter_dump(const FLAC__BitWriter *bw, FILE *out) | |||||
{ | |||||
unsigned i, j; | |||||
if(bw == 0) { | |||||
fprintf(out, "bitwriter is NULL\n"); | |||||
} | |||||
else { | |||||
fprintf(out, "bitwriter: capacity=%u words=%u bits=%u total_bits=%u\n", bw->capacity, bw->words, bw->bits, FLAC__TOTAL_BITS(bw)); | |||||
for(i = 0; i < bw->words; i++) { | |||||
fprintf(out, "%08X: ", i); | |||||
for(j = 0; j < FLAC__BITS_PER_WORD; j++) | |||||
fprintf(out, "%01u", bw->buffer[i] & (1 << (FLAC__BITS_PER_WORD-j-1)) ? 1:0); | |||||
fprintf(out, "\n"); | |||||
} | |||||
if(bw->bits > 0) { | |||||
fprintf(out, "%08X: ", i); | |||||
for(j = 0; j < bw->bits; j++) | |||||
fprintf(out, "%01u", bw->accum & (1 << (bw->bits-j-1)) ? 1:0); | |||||
fprintf(out, "\n"); | |||||
} | |||||
} | |||||
} | |||||
FLAC__bool FLAC__bitwriter_get_write_crc16(FLAC__BitWriter *bw, FLAC__uint16 *crc) | |||||
{ | |||||
const FLAC__byte *buffer; | |||||
size_t bytes; | |||||
FLAC__ASSERT((bw->bits & 7) == 0); /* assert that we're byte-aligned */ | |||||
if(!FLAC__bitwriter_get_buffer(bw, &buffer, &bytes)) | |||||
return false; | |||||
*crc = (FLAC__uint16)FLAC__crc16(buffer, bytes); | |||||
FLAC__bitwriter_release_buffer(bw); | |||||
return true; | |||||
} | |||||
FLAC__bool FLAC__bitwriter_get_write_crc8(FLAC__BitWriter *bw, FLAC__byte *crc) | |||||
{ | |||||
const FLAC__byte *buffer; | |||||
size_t bytes; | |||||
FLAC__ASSERT((bw->bits & 7) == 0); /* assert that we're byte-aligned */ | |||||
if(!FLAC__bitwriter_get_buffer(bw, &buffer, &bytes)) | |||||
return false; | |||||
*crc = FLAC__crc8(buffer, bytes); | |||||
FLAC__bitwriter_release_buffer(bw); | |||||
return true; | |||||
} | |||||
FLAC__bool FLAC__bitwriter_is_byte_aligned(const FLAC__BitWriter *bw) | |||||
{ | |||||
return ((bw->bits & 7) == 0); | |||||
} | |||||
unsigned FLAC__bitwriter_get_input_bits_unconsumed(const FLAC__BitWriter *bw) | |||||
{ | |||||
return FLAC__TOTAL_BITS(bw); | |||||
} | |||||
FLAC__bool FLAC__bitwriter_get_buffer(FLAC__BitWriter *bw, const FLAC__byte **buffer, size_t *bytes) | |||||
{ | |||||
FLAC__ASSERT((bw->bits & 7) == 0); | |||||
/* double protection */ | |||||
if(bw->bits & 7) | |||||
return false; | |||||
/* if we have bits in the accumulator we have to flush those to the buffer first */ | |||||
if(bw->bits) { | |||||
FLAC__ASSERT(bw->words <= bw->capacity); | |||||
if(bw->words == bw->capacity && !bitwriter_grow_(bw, FLAC__BITS_PER_WORD)) | |||||
return false; | |||||
/* append bits as complete word to buffer, but don't change bw->accum or bw->bits */ | |||||
bw->buffer[bw->words] = SWAP_BE_WORD_TO_HOST(bw->accum << (FLAC__BITS_PER_WORD-bw->bits)); | |||||
} | |||||
/* now we can just return what we have */ | |||||
*buffer = (FLAC__byte*)bw->buffer; | |||||
*bytes = (FLAC__BYTES_PER_WORD * bw->words) + (bw->bits >> 3); | |||||
return true; | |||||
} | |||||
void FLAC__bitwriter_release_buffer(FLAC__BitWriter *bw) | |||||
{ | |||||
/* nothing to do. in the future, strict checking of a 'writer-is-in- | |||||
* get-mode' flag could be added everywhere and then cleared here | |||||
*/ | |||||
(void)bw; | |||||
} | |||||
FLaC__INLINE FLAC__bool FLAC__bitwriter_write_zeroes(FLAC__BitWriter *bw, unsigned bits) | |||||
{ | |||||
unsigned n; | |||||
FLAC__ASSERT(0 != bw); | |||||
FLAC__ASSERT(0 != bw->buffer); | |||||
if(bits == 0) | |||||
return true; | |||||
/* slightly pessimistic size check but faster than "<= bw->words + (bw->bits+bits+FLAC__BITS_PER_WORD-1)/FLAC__BITS_PER_WORD" */ | |||||
if(bw->capacity <= bw->words + bits && !bitwriter_grow_(bw, bits)) | |||||
return false; | |||||
/* first part gets to word alignment */ | |||||
if(bw->bits) { | |||||
n = min(FLAC__BITS_PER_WORD - bw->bits, bits); | |||||
bw->accum <<= n; | |||||
bits -= n; | |||||
bw->bits += n; | |||||
if(bw->bits == FLAC__BITS_PER_WORD) { | |||||
bw->buffer[bw->words++] = SWAP_BE_WORD_TO_HOST(bw->accum); | |||||
bw->bits = 0; | |||||
} | |||||
else | |||||
return true; | |||||
} | |||||
/* do whole words */ | |||||
while(bits >= FLAC__BITS_PER_WORD) { | |||||
bw->buffer[bw->words++] = 0; | |||||
bits -= FLAC__BITS_PER_WORD; | |||||
} | |||||
/* do any leftovers */ | |||||
if(bits > 0) { | |||||
bw->accum = 0; | |||||
bw->bits = bits; | |||||
} | |||||
return true; | |||||
} | |||||
FLaC__INLINE FLAC__bool FLAC__bitwriter_write_raw_uint32(FLAC__BitWriter *bw, FLAC__uint32 val, unsigned bits) | |||||
{ | |||||
register unsigned left; | |||||
/* WATCHOUT: code does not work with <32bit words; we can make things much faster with this assertion */ | |||||
FLAC__ASSERT(FLAC__BITS_PER_WORD >= 32); | |||||
FLAC__ASSERT(0 != bw); | |||||
FLAC__ASSERT(0 != bw->buffer); | |||||
FLAC__ASSERT(bits <= 32); | |||||
if(bits == 0) | |||||
return true; | |||||
/* slightly pessimistic size check but faster than "<= bw->words + (bw->bits+bits+FLAC__BITS_PER_WORD-1)/FLAC__BITS_PER_WORD" */ | |||||
if(bw->capacity <= bw->words + bits && !bitwriter_grow_(bw, bits)) | |||||
return false; | |||||
left = FLAC__BITS_PER_WORD - bw->bits; | |||||
if(bits < left) { | |||||
bw->accum <<= bits; | |||||
bw->accum |= val; | |||||
bw->bits += bits; | |||||
} | |||||
else if(bw->bits) { /* WATCHOUT: if bw->bits == 0, left==FLAC__BITS_PER_WORD and bw->accum<<=left is a NOP instead of setting to 0 */ | |||||
bw->accum <<= left; | |||||
bw->accum |= val >> (bw->bits = bits - left); | |||||
bw->buffer[bw->words++] = SWAP_BE_WORD_TO_HOST(bw->accum); | |||||
bw->accum = val; | |||||
} | |||||
else { | |||||
bw->accum = val; | |||||
bw->bits = 0; | |||||
bw->buffer[bw->words++] = SWAP_BE_WORD_TO_HOST(val); | |||||
} | |||||
return true; | |||||
} | |||||
FLaC__INLINE FLAC__bool FLAC__bitwriter_write_raw_int32(FLAC__BitWriter *bw, FLAC__int32 val, unsigned bits) | |||||
{ | |||||
/* zero-out unused bits */ | |||||
if(bits < 32) | |||||
val &= (~(0xffffffff << bits)); | |||||
return FLAC__bitwriter_write_raw_uint32(bw, (FLAC__uint32)val, bits); | |||||
} | |||||
FLaC__INLINE FLAC__bool FLAC__bitwriter_write_raw_uint64(FLAC__BitWriter *bw, FLAC__uint64 val, unsigned bits) | |||||
{ | |||||
/* this could be a little faster but it's not used for much */ | |||||
if(bits > 32) { | |||||
return | |||||
FLAC__bitwriter_write_raw_uint32(bw, (FLAC__uint32)(val>>32), bits-32) && | |||||
FLAC__bitwriter_write_raw_uint32(bw, (FLAC__uint32)val, 32); | |||||
} | |||||
else | |||||
return FLAC__bitwriter_write_raw_uint32(bw, (FLAC__uint32)val, bits); | |||||
} | |||||
FLaC__INLINE FLAC__bool FLAC__bitwriter_write_raw_uint32_little_endian(FLAC__BitWriter *bw, FLAC__uint32 val) | |||||
{ | |||||
/* this doesn't need to be that fast as currently it is only used for vorbis comments */ | |||||
if(!FLAC__bitwriter_write_raw_uint32(bw, val & 0xff, 8)) | |||||
return false; | |||||
if(!FLAC__bitwriter_write_raw_uint32(bw, (val>>8) & 0xff, 8)) | |||||
return false; | |||||
if(!FLAC__bitwriter_write_raw_uint32(bw, (val>>16) & 0xff, 8)) | |||||
return false; | |||||
if(!FLAC__bitwriter_write_raw_uint32(bw, val>>24, 8)) | |||||
return false; | |||||
return true; | |||||
} | |||||
FLaC__INLINE FLAC__bool FLAC__bitwriter_write_byte_block(FLAC__BitWriter *bw, const FLAC__byte vals[], unsigned nvals) | |||||
{ | |||||
unsigned i; | |||||
/* this could be faster but currently we don't need it to be since it's only used for writing metadata */ | |||||
for(i = 0; i < nvals; i++) { | |||||
if(!FLAC__bitwriter_write_raw_uint32(bw, (FLAC__uint32)(vals[i]), 8)) | |||||
return false; | |||||
} | |||||
return true; | |||||
} | |||||
FLAC__bool FLAC__bitwriter_write_unary_unsigned(FLAC__BitWriter *bw, unsigned val) | |||||
{ | |||||
if(val < 32) | |||||
return FLAC__bitwriter_write_raw_uint32(bw, 1, ++val); | |||||
else | |||||
return | |||||
FLAC__bitwriter_write_zeroes(bw, val) && | |||||
FLAC__bitwriter_write_raw_uint32(bw, 1, 1); | |||||
} | |||||
unsigned FLAC__bitwriter_rice_bits(FLAC__int32 val, unsigned parameter) | |||||
{ | |||||
FLAC__uint32 uval; | |||||
FLAC__ASSERT(parameter < sizeof(unsigned)*8); | |||||
/* fold signed to unsigned; actual formula is: negative(v)? -2v-1 : 2v */ | |||||
uval = (val<<1) ^ (val>>31); | |||||
return 1 + parameter + (uval >> parameter); | |||||
} | |||||
#if 0 /* UNUSED */ | |||||
unsigned FLAC__bitwriter_golomb_bits_signed(int val, unsigned parameter) | |||||
{ | |||||
unsigned bits, msbs, uval; | |||||
unsigned k; | |||||
FLAC__ASSERT(parameter > 0); | |||||
/* fold signed to unsigned */ | |||||
if(val < 0) | |||||
uval = (unsigned)(((-(++val)) << 1) + 1); | |||||
else | |||||
uval = (unsigned)(val << 1); | |||||
k = FLAC__bitmath_ilog2(parameter); | |||||
if(parameter == 1u<<k) { | |||||
FLAC__ASSERT(k <= 30); | |||||
msbs = uval >> k; | |||||
bits = 1 + k + msbs; | |||||
} | |||||
else { | |||||
unsigned q, r, d; | |||||
d = (1 << (k+1)) - parameter; | |||||
q = uval / parameter; | |||||
r = uval - (q * parameter); | |||||
bits = 1 + q + k; | |||||
if(r >= d) | |||||
bits++; | |||||
} | |||||
return bits; | |||||
} | |||||
unsigned FLAC__bitwriter_golomb_bits_unsigned(unsigned uval, unsigned parameter) | |||||
{ | |||||
unsigned bits, msbs; | |||||
unsigned k; | |||||
FLAC__ASSERT(parameter > 0); | |||||
k = FLAC__bitmath_ilog2(parameter); | |||||
if(parameter == 1u<<k) { | |||||
FLAC__ASSERT(k <= 30); | |||||
msbs = uval >> k; | |||||
bits = 1 + k + msbs; | |||||
} | |||||
else { | |||||
unsigned q, r, d; | |||||
d = (1 << (k+1)) - parameter; | |||||
q = uval / parameter; | |||||
r = uval - (q * parameter); | |||||
bits = 1 + q + k; | |||||
if(r >= d) | |||||
bits++; | |||||
} | |||||
return bits; | |||||
} | |||||
#endif /* UNUSED */ | |||||
FLAC__bool FLAC__bitwriter_write_rice_signed(FLAC__BitWriter *bw, FLAC__int32 val, unsigned parameter) | |||||
{ | |||||
unsigned total_bits, interesting_bits, msbs; | |||||
FLAC__uint32 uval, pattern; | |||||
FLAC__ASSERT(0 != bw); | |||||
FLAC__ASSERT(0 != bw->buffer); | |||||
FLAC__ASSERT(parameter < 8*sizeof(uval)); | |||||
/* fold signed to unsigned; actual formula is: negative(v)? -2v-1 : 2v */ | |||||
uval = (val<<1) ^ (val>>31); | |||||
msbs = uval >> parameter; | |||||
interesting_bits = 1 + parameter; | |||||
total_bits = interesting_bits + msbs; | |||||
pattern = 1 << parameter; /* the unary end bit */ | |||||
pattern |= (uval & ((1<<parameter)-1)); /* the binary LSBs */ | |||||
if(total_bits <= 32) | |||||
return FLAC__bitwriter_write_raw_uint32(bw, pattern, total_bits); | |||||
else | |||||
return | |||||
FLAC__bitwriter_write_zeroes(bw, msbs) && /* write the unary MSBs */ | |||||
FLAC__bitwriter_write_raw_uint32(bw, pattern, interesting_bits); /* write the unary end bit and binary LSBs */ | |||||
} | |||||
FLAC__bool FLAC__bitwriter_write_rice_signed_block(FLAC__BitWriter *bw, const FLAC__int32 *vals, unsigned nvals, unsigned parameter) | |||||
{ | |||||
const FLAC__uint32 mask1 = FLAC__WORD_ALL_ONES << parameter; /* we val|=mask1 to set the stop bit above it... */ | |||||
const FLAC__uint32 mask2 = FLAC__WORD_ALL_ONES >> (31-parameter); /* ...then mask off the bits above the stop bit with val&=mask2*/ | |||||
FLAC__uint32 uval; | |||||
unsigned left; | |||||
const unsigned lsbits = 1 + parameter; | |||||
unsigned msbits; | |||||
FLAC__ASSERT(0 != bw); | |||||
FLAC__ASSERT(0 != bw->buffer); | |||||
FLAC__ASSERT(parameter < 8*sizeof(bwword)-1); | |||||
/* WATCHOUT: code does not work with <32bit words; we can make things much faster with this assertion */ | |||||
FLAC__ASSERT(FLAC__BITS_PER_WORD >= 32); | |||||
while(nvals) { | |||||
/* fold signed to unsigned; actual formula is: negative(v)? -2v-1 : 2v */ | |||||
uval = (*vals<<1) ^ (*vals>>31); | |||||
msbits = uval >> parameter; | |||||
#if 0 /* OPT: can remove this special case if it doesn't make up for the extra compare (doesn't make a statistically significant difference with msvc or gcc/x86) */ | |||||
if(bw->bits && bw->bits + msbits + lsbits <= FLAC__BITS_PER_WORD) { /* i.e. if the whole thing fits in the current bwword */ | |||||
/* ^^^ if bw->bits is 0 then we may have filled the buffer and have no free bwword to work in */ | |||||
bw->bits = bw->bits + msbits + lsbits; | |||||
uval |= mask1; /* set stop bit */ | |||||
uval &= mask2; /* mask off unused top bits */ | |||||
/* NOT: bw->accum <<= msbits + lsbits because msbits+lsbits could be 32, then the shift would be a NOP */ | |||||
bw->accum <<= msbits; | |||||
bw->accum <<= lsbits; | |||||
bw->accum |= uval; | |||||
if(bw->bits == FLAC__BITS_PER_WORD) { | |||||
bw->buffer[bw->words++] = SWAP_BE_WORD_TO_HOST(bw->accum); | |||||
bw->bits = 0; | |||||
/* burying the capacity check down here means we have to grow the buffer a little if there are more vals to do */ | |||||
if(bw->capacity <= bw->words && nvals > 1 && !bitwriter_grow_(bw, 1)) { | |||||
FLAC__ASSERT(bw->capacity == bw->words); | |||||
return false; | |||||
} | |||||
} | |||||
} | |||||
else { | |||||
#elif 1 /*@@@@@@ OPT: try this version with MSVC6 to see if better, not much difference for gcc-4 */ | |||||
if(bw->bits && bw->bits + msbits + lsbits < FLAC__BITS_PER_WORD) { /* i.e. if the whole thing fits in the current bwword */ | |||||
/* ^^^ if bw->bits is 0 then we may have filled the buffer and have no free bwword to work in */ | |||||
bw->bits = bw->bits + msbits + lsbits; | |||||
uval |= mask1; /* set stop bit */ | |||||
uval &= mask2; /* mask off unused top bits */ | |||||
bw->accum <<= msbits + lsbits; | |||||
bw->accum |= uval; | |||||
} | |||||
else { | |||||
#endif | |||||
/* slightly pessimistic size check but faster than "<= bw->words + (bw->bits+msbits+lsbits+FLAC__BITS_PER_WORD-1)/FLAC__BITS_PER_WORD" */ | |||||
/* OPT: pessimism may cause flurry of false calls to grow_ which eat up all savings before it */ | |||||
if(bw->capacity <= bw->words + bw->bits + msbits + 1/*lsbits always fit in 1 bwword*/ && !bitwriter_grow_(bw, msbits+lsbits)) | |||||
return false; | |||||
if(msbits) { | |||||
/* first part gets to word alignment */ | |||||
if(bw->bits) { | |||||
left = FLAC__BITS_PER_WORD - bw->bits; | |||||
if(msbits < left) { | |||||
bw->accum <<= msbits; | |||||
bw->bits += msbits; | |||||
goto break1; | |||||
} | |||||
else { | |||||
bw->accum <<= left; | |||||
msbits -= left; | |||||
bw->buffer[bw->words++] = SWAP_BE_WORD_TO_HOST(bw->accum); | |||||
bw->bits = 0; | |||||
} | |||||
} | |||||
/* do whole words */ | |||||
while(msbits >= FLAC__BITS_PER_WORD) { | |||||
bw->buffer[bw->words++] = 0; | |||||
msbits -= FLAC__BITS_PER_WORD; | |||||
} | |||||
/* do any leftovers */ | |||||
if(msbits > 0) { | |||||
bw->accum = 0; | |||||
bw->bits = msbits; | |||||
} | |||||
} | |||||
break1: | |||||
uval |= mask1; /* set stop bit */ | |||||
uval &= mask2; /* mask off unused top bits */ | |||||
left = FLAC__BITS_PER_WORD - bw->bits; | |||||
if(lsbits < left) { | |||||
bw->accum <<= lsbits; | |||||
bw->accum |= uval; | |||||
bw->bits += lsbits; | |||||
} | |||||
else { | |||||
/* if bw->bits == 0, left==FLAC__BITS_PER_WORD which will always | |||||
* be > lsbits (because of previous assertions) so it would have | |||||
* triggered the (lsbits<left) case above. | |||||
*/ | |||||
FLAC__ASSERT(bw->bits); | |||||
FLAC__ASSERT(left < FLAC__BITS_PER_WORD); | |||||
bw->accum <<= left; | |||||
bw->accum |= uval >> (bw->bits = lsbits - left); | |||||
bw->buffer[bw->words++] = SWAP_BE_WORD_TO_HOST(bw->accum); | |||||
bw->accum = uval; | |||||
} | |||||
#if 1 | |||||
} | |||||
#endif | |||||
vals++; | |||||
nvals--; | |||||
} | |||||
return true; | |||||
} | |||||
#if 0 /* UNUSED */ | |||||
FLAC__bool FLAC__bitwriter_write_golomb_signed(FLAC__BitWriter *bw, int val, unsigned parameter) | |||||
{ | |||||
unsigned total_bits, msbs, uval; | |||||
unsigned k; | |||||
FLAC__ASSERT(0 != bw); | |||||
FLAC__ASSERT(0 != bw->buffer); | |||||
FLAC__ASSERT(parameter > 0); | |||||
/* fold signed to unsigned */ | |||||
if(val < 0) | |||||
uval = (unsigned)(((-(++val)) << 1) + 1); | |||||
else | |||||
uval = (unsigned)(val << 1); | |||||
k = FLAC__bitmath_ilog2(parameter); | |||||
if(parameter == 1u<<k) { | |||||
unsigned pattern; | |||||
FLAC__ASSERT(k <= 30); | |||||
msbs = uval >> k; | |||||
total_bits = 1 + k + msbs; | |||||
pattern = 1 << k; /* the unary end bit */ | |||||
pattern |= (uval & ((1u<<k)-1)); /* the binary LSBs */ | |||||
if(total_bits <= 32) { | |||||
if(!FLAC__bitwriter_write_raw_uint32(bw, pattern, total_bits)) | |||||
return false; | |||||
} | |||||
else { | |||||
/* write the unary MSBs */ | |||||
if(!FLAC__bitwriter_write_zeroes(bw, msbs)) | |||||
return false; | |||||
/* write the unary end bit and binary LSBs */ | |||||
if(!FLAC__bitwriter_write_raw_uint32(bw, pattern, k+1)) | |||||
return false; | |||||
} | |||||
} | |||||
else { | |||||
unsigned q, r, d; | |||||
d = (1 << (k+1)) - parameter; | |||||
q = uval / parameter; | |||||
r = uval - (q * parameter); | |||||
/* write the unary MSBs */ | |||||
if(!FLAC__bitwriter_write_zeroes(bw, q)) | |||||
return false; | |||||
/* write the unary end bit */ | |||||
if(!FLAC__bitwriter_write_raw_uint32(bw, 1, 1)) | |||||
return false; | |||||
/* write the binary LSBs */ | |||||
if(r >= d) { | |||||
if(!FLAC__bitwriter_write_raw_uint32(bw, r+d, k+1)) | |||||
return false; | |||||
} | |||||
else { | |||||
if(!FLAC__bitwriter_write_raw_uint32(bw, r, k)) | |||||
return false; | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
FLAC__bool FLAC__bitwriter_write_golomb_unsigned(FLAC__BitWriter *bw, unsigned uval, unsigned parameter) | |||||
{ | |||||
unsigned total_bits, msbs; | |||||
unsigned k; | |||||
FLAC__ASSERT(0 != bw); | |||||
FLAC__ASSERT(0 != bw->buffer); | |||||
FLAC__ASSERT(parameter > 0); | |||||
k = FLAC__bitmath_ilog2(parameter); | |||||
if(parameter == 1u<<k) { | |||||
unsigned pattern; | |||||
FLAC__ASSERT(k <= 30); | |||||
msbs = uval >> k; | |||||
total_bits = 1 + k + msbs; | |||||
pattern = 1 << k; /* the unary end bit */ | |||||
pattern |= (uval & ((1u<<k)-1)); /* the binary LSBs */ | |||||
if(total_bits <= 32) { | |||||
if(!FLAC__bitwriter_write_raw_uint32(bw, pattern, total_bits)) | |||||
return false; | |||||
} | |||||
else { | |||||
/* write the unary MSBs */ | |||||
if(!FLAC__bitwriter_write_zeroes(bw, msbs)) | |||||
return false; | |||||
/* write the unary end bit and binary LSBs */ | |||||
if(!FLAC__bitwriter_write_raw_uint32(bw, pattern, k+1)) | |||||
return false; | |||||
} | |||||
} | |||||
else { | |||||
unsigned q, r, d; | |||||
d = (1 << (k+1)) - parameter; | |||||
q = uval / parameter; | |||||
r = uval - (q * parameter); | |||||
/* write the unary MSBs */ | |||||
if(!FLAC__bitwriter_write_zeroes(bw, q)) | |||||
return false; | |||||
/* write the unary end bit */ | |||||
if(!FLAC__bitwriter_write_raw_uint32(bw, 1, 1)) | |||||
return false; | |||||
/* write the binary LSBs */ | |||||
if(r >= d) { | |||||
if(!FLAC__bitwriter_write_raw_uint32(bw, r+d, k+1)) | |||||
return false; | |||||
} | |||||
else { | |||||
if(!FLAC__bitwriter_write_raw_uint32(bw, r, k)) | |||||
return false; | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
#endif /* UNUSED */ | |||||
FLAC__bool FLAC__bitwriter_write_utf8_uint32(FLAC__BitWriter *bw, FLAC__uint32 val) | |||||
{ | |||||
FLAC__bool ok = 1; | |||||
FLAC__ASSERT(0 != bw); | |||||
FLAC__ASSERT(0 != bw->buffer); | |||||
FLAC__ASSERT(!(val & 0x80000000)); /* this version only handles 31 bits */ | |||||
if(val < 0x80) { | |||||
return FLAC__bitwriter_write_raw_uint32(bw, val, 8); | |||||
} | |||||
else if(val < 0x800) { | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0xC0 | (val>>6), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (val&0x3F), 8); | |||||
} | |||||
else if(val < 0x10000) { | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0xE0 | (val>>12), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | ((val>>6)&0x3F), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (val&0x3F), 8); | |||||
} | |||||
else if(val < 0x200000) { | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0xF0 | (val>>18), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | ((val>>12)&0x3F), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | ((val>>6)&0x3F), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (val&0x3F), 8); | |||||
} | |||||
else if(val < 0x4000000) { | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0xF8 | (val>>24), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | ((val>>18)&0x3F), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | ((val>>12)&0x3F), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | ((val>>6)&0x3F), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (val&0x3F), 8); | |||||
} | |||||
else { | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0xFC | (val>>30), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | ((val>>24)&0x3F), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | ((val>>18)&0x3F), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | ((val>>12)&0x3F), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | ((val>>6)&0x3F), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (val&0x3F), 8); | |||||
} | |||||
return ok; | |||||
} | |||||
FLAC__bool FLAC__bitwriter_write_utf8_uint64(FLAC__BitWriter *bw, FLAC__uint64 val) | |||||
{ | |||||
FLAC__bool ok = 1; | |||||
FLAC__ASSERT(0 != bw); | |||||
FLAC__ASSERT(0 != bw->buffer); | |||||
FLAC__ASSERT(!(val & FLAC__U64L(0xFFFFFFF000000000))); /* this version only handles 36 bits */ | |||||
if(val < 0x80) { | |||||
return FLAC__bitwriter_write_raw_uint32(bw, (FLAC__uint32)val, 8); | |||||
} | |||||
else if(val < 0x800) { | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0xC0 | (FLAC__uint32)(val>>6), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (FLAC__uint32)(val&0x3F), 8); | |||||
} | |||||
else if(val < 0x10000) { | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0xE0 | (FLAC__uint32)(val>>12), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (FLAC__uint32)((val>>6)&0x3F), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (FLAC__uint32)(val&0x3F), 8); | |||||
} | |||||
else if(val < 0x200000) { | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0xF0 | (FLAC__uint32)(val>>18), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (FLAC__uint32)((val>>12)&0x3F), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (FLAC__uint32)((val>>6)&0x3F), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (FLAC__uint32)(val&0x3F), 8); | |||||
} | |||||
else if(val < 0x4000000) { | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0xF8 | (FLAC__uint32)(val>>24), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (FLAC__uint32)((val>>18)&0x3F), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (FLAC__uint32)((val>>12)&0x3F), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (FLAC__uint32)((val>>6)&0x3F), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (FLAC__uint32)(val&0x3F), 8); | |||||
} | |||||
else if(val < 0x80000000) { | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0xFC | (FLAC__uint32)(val>>30), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (FLAC__uint32)((val>>24)&0x3F), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (FLAC__uint32)((val>>18)&0x3F), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (FLAC__uint32)((val>>12)&0x3F), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (FLAC__uint32)((val>>6)&0x3F), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (FLAC__uint32)(val&0x3F), 8); | |||||
} | |||||
else { | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0xFE, 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (FLAC__uint32)((val>>30)&0x3F), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (FLAC__uint32)((val>>24)&0x3F), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (FLAC__uint32)((val>>18)&0x3F), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (FLAC__uint32)((val>>12)&0x3F), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (FLAC__uint32)((val>>6)&0x3F), 8); | |||||
ok &= FLAC__bitwriter_write_raw_uint32(bw, 0x80 | (FLAC__uint32)(val&0x3F), 8); | |||||
} | |||||
return ok; | |||||
} | |||||
FLAC__bool FLAC__bitwriter_zero_pad_to_byte_boundary(FLAC__BitWriter *bw) | |||||
{ | |||||
/* 0-pad to byte boundary */ | |||||
if(bw->bits & 7u) | |||||
return FLAC__bitwriter_write_zeroes(bw, 8 - (bw->bits & 7u)); | |||||
else | |||||
return true; | |||||
} |
@@ -0,0 +1,418 @@ | |||||
/* libFLAC - Free Lossless Audio Codec library | |||||
* Copyright (C) 2001,2002,2003,2004,2005,2006,2007 Josh Coalson | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions | |||||
* are met: | |||||
* | |||||
* - Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* | |||||
* - Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* | |||||
* - Neither the name of the Xiph.org Foundation nor the names of its | |||||
* contributors may be used to endorse or promote products derived from | |||||
* this software without specific prior written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR | |||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
*/ | |||||
#if HAVE_CONFIG_H | |||||
# include <config.h> | |||||
#endif | |||||
#include "include/private/cpu.h" | |||||
#include <stdlib.h> | |||||
#include <stdio.h> | |||||
#if defined FLAC__CPU_IA32 | |||||
# include <signal.h> | |||||
#elif defined FLAC__CPU_PPC | |||||
# if !defined FLAC__NO_ASM | |||||
# if defined FLAC__SYS_DARWIN | |||||
# include <sys/sysctl.h> | |||||
# include <mach/mach.h> | |||||
# include <mach/mach_host.h> | |||||
# include <mach/host_info.h> | |||||
# include <mach/machine.h> | |||||
# ifndef CPU_SUBTYPE_POWERPC_970 | |||||
# define CPU_SUBTYPE_POWERPC_970 ((cpu_subtype_t) 100) | |||||
# endif | |||||
# else /* FLAC__SYS_DARWIN */ | |||||
# include <signal.h> | |||||
# include <setjmp.h> | |||||
static sigjmp_buf jmpbuf; | |||||
static volatile sig_atomic_t canjump = 0; | |||||
static void sigill_handler (int sig) | |||||
{ | |||||
if (!canjump) { | |||||
signal (sig, SIG_DFL); | |||||
raise (sig); | |||||
} | |||||
canjump = 0; | |||||
siglongjmp (jmpbuf, 1); | |||||
} | |||||
# endif /* FLAC__SYS_DARWIN */ | |||||
# endif /* FLAC__NO_ASM */ | |||||
#endif /* FLAC__CPU_PPC */ | |||||
#if defined (__NetBSD__) || defined(__OpenBSD__) | |||||
#include <sys/param.h> | |||||
#include <sys/sysctl.h> | |||||
#include <machine/cpu.h> | |||||
#endif | |||||
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) | |||||
#include <sys/types.h> | |||||
#include <sys/sysctl.h> | |||||
#endif | |||||
#if defined(__APPLE__) | |||||
/* how to get sysctlbyname()? */ | |||||
#endif | |||||
/* these are flags in EDX of CPUID AX=00000001 */ | |||||
static const unsigned FLAC__CPUINFO_IA32_CPUID_CMOV = 0x00008000; | |||||
static const unsigned FLAC__CPUINFO_IA32_CPUID_MMX = 0x00800000; | |||||
static const unsigned FLAC__CPUINFO_IA32_CPUID_FXSR = 0x01000000; | |||||
static const unsigned FLAC__CPUINFO_IA32_CPUID_SSE = 0x02000000; | |||||
static const unsigned FLAC__CPUINFO_IA32_CPUID_SSE2 = 0x04000000; | |||||
/* these are flags in ECX of CPUID AX=00000001 */ | |||||
static const unsigned FLAC__CPUINFO_IA32_CPUID_SSE3 = 0x00000001; | |||||
static const unsigned FLAC__CPUINFO_IA32_CPUID_SSSE3 = 0x00000200; | |||||
/* these are flags in EDX of CPUID AX=80000001 */ | |||||
static const unsigned FLAC__CPUINFO_IA32_CPUID_EXTENDED_AMD_3DNOW = 0x80000000; | |||||
static const unsigned FLAC__CPUINFO_IA32_CPUID_EXTENDED_AMD_EXT3DNOW = 0x40000000; | |||||
static const unsigned FLAC__CPUINFO_IA32_CPUID_EXTENDED_AMD_EXTMMX = 0x00400000; | |||||
/* | |||||
* Extra stuff needed for detection of OS support for SSE on IA-32 | |||||
*/ | |||||
#if defined(FLAC__CPU_IA32) && !defined FLAC__NO_ASM && defined FLAC__HAS_NASM && !defined FLAC__NO_SSE_OS && !defined FLAC__SSE_OS | |||||
# if defined(__linux__) | |||||
/* | |||||
* If the OS doesn't support SSE, we will get here with a SIGILL. We | |||||
* modify the return address to jump over the offending SSE instruction | |||||
* and also the operation following it that indicates the instruction | |||||
* executed successfully. In this way we use no global variables and | |||||
* stay thread-safe. | |||||
* | |||||
* 3 + 3 + 6: | |||||
* 3 bytes for "xorps xmm0,xmm0" | |||||
* 3 bytes for estimate of how long the follwing "inc var" instruction is | |||||
* 6 bytes extra in case our estimate is wrong | |||||
* 12 bytes puts us in the NOP "landing zone" | |||||
*/ | |||||
# undef USE_OBSOLETE_SIGCONTEXT_FLAVOR /* #define this to use the older signal handler method */ | |||||
# ifdef USE_OBSOLETE_SIGCONTEXT_FLAVOR | |||||
static void sigill_handler_sse_os(int signal, struct sigcontext sc) | |||||
{ | |||||
(void)signal; | |||||
sc.eip += 3 + 3 + 6; | |||||
} | |||||
# else | |||||
# include <sys/ucontext.h> | |||||
static void sigill_handler_sse_os(int signal, siginfo_t *si, void *uc) | |||||
{ | |||||
(void)signal, (void)si; | |||||
((ucontext_t*)uc)->uc_mcontext.gregs[14/*REG_EIP*/] += 3 + 3 + 6; | |||||
} | |||||
# endif | |||||
# elif defined(_MSC_VER) | |||||
# include <windows.h> | |||||
# undef USE_TRY_CATCH_FLAVOR /* #define this to use the try/catch method for catching illegal opcode exception */ | |||||
# ifdef USE_TRY_CATCH_FLAVOR | |||||
# else | |||||
LONG CALLBACK sigill_handler_sse_os(EXCEPTION_POINTERS *ep) | |||||
{ | |||||
if(ep->ExceptionRecord->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION) { | |||||
ep->ContextRecord->Eip += 3 + 3 + 6; | |||||
return EXCEPTION_CONTINUE_EXECUTION; | |||||
} | |||||
return EXCEPTION_CONTINUE_SEARCH; | |||||
} | |||||
# endif | |||||
# endif | |||||
#endif | |||||
void FLAC__cpu_info(FLAC__CPUInfo *info) | |||||
{ | |||||
/* | |||||
* IA32-specific | |||||
*/ | |||||
#ifdef FLAC__CPU_IA32 | |||||
info->type = FLAC__CPUINFO_TYPE_IA32; | |||||
#if !defined FLAC__NO_ASM && defined FLAC__HAS_NASM | |||||
info->use_asm = true; /* we assume a minimum of 80386 with FLAC__CPU_IA32 */ | |||||
info->data.ia32.cpuid = FLAC__cpu_have_cpuid_asm_ia32()? true : false; | |||||
info->data.ia32.bswap = info->data.ia32.cpuid; /* CPUID => BSWAP since it came after */ | |||||
info->data.ia32.cmov = false; | |||||
info->data.ia32.mmx = false; | |||||
info->data.ia32.fxsr = false; | |||||
info->data.ia32.sse = false; | |||||
info->data.ia32.sse2 = false; | |||||
info->data.ia32.sse3 = false; | |||||
info->data.ia32.ssse3 = false; | |||||
info->data.ia32._3dnow = false; | |||||
info->data.ia32.ext3dnow = false; | |||||
info->data.ia32.extmmx = false; | |||||
if(info->data.ia32.cpuid) { | |||||
/* http://www.sandpile.org/ia32/cpuid.htm */ | |||||
FLAC__uint32 flags_edx, flags_ecx; | |||||
FLAC__cpu_info_asm_ia32(&flags_edx, &flags_ecx); | |||||
info->data.ia32.cmov = (flags_edx & FLAC__CPUINFO_IA32_CPUID_CMOV )? true : false; | |||||
info->data.ia32.mmx = (flags_edx & FLAC__CPUINFO_IA32_CPUID_MMX )? true : false; | |||||
info->data.ia32.fxsr = (flags_edx & FLAC__CPUINFO_IA32_CPUID_FXSR )? true : false; | |||||
info->data.ia32.sse = (flags_edx & FLAC__CPUINFO_IA32_CPUID_SSE )? true : false; | |||||
info->data.ia32.sse2 = (flags_edx & FLAC__CPUINFO_IA32_CPUID_SSE2 )? true : false; | |||||
info->data.ia32.sse3 = (flags_ecx & FLAC__CPUINFO_IA32_CPUID_SSE3 )? true : false; | |||||
info->data.ia32.ssse3 = (flags_ecx & FLAC__CPUINFO_IA32_CPUID_SSSE3)? true : false; | |||||
#ifdef FLAC__USE_3DNOW | |||||
flags_edx = FLAC__cpu_info_extended_amd_asm_ia32(); | |||||
info->data.ia32._3dnow = (flags_edx & FLAC__CPUINFO_IA32_CPUID_EXTENDED_AMD_3DNOW )? true : false; | |||||
info->data.ia32.ext3dnow = (flags_edx & FLAC__CPUINFO_IA32_CPUID_EXTENDED_AMD_EXT3DNOW)? true : false; | |||||
info->data.ia32.extmmx = (flags_edx & FLAC__CPUINFO_IA32_CPUID_EXTENDED_AMD_EXTMMX )? true : false; | |||||
#else | |||||
info->data.ia32._3dnow = info->data.ia32.ext3dnow = info->data.ia32.extmmx = false; | |||||
#endif | |||||
#ifdef DEBUG | |||||
fprintf(stderr, "CPU info (IA-32):\n"); | |||||
fprintf(stderr, " CPUID ...... %c\n", info->data.ia32.cpuid ? 'Y' : 'n'); | |||||
fprintf(stderr, " BSWAP ...... %c\n", info->data.ia32.bswap ? 'Y' : 'n'); | |||||
fprintf(stderr, " CMOV ....... %c\n", info->data.ia32.cmov ? 'Y' : 'n'); | |||||
fprintf(stderr, " MMX ........ %c\n", info->data.ia32.mmx ? 'Y' : 'n'); | |||||
fprintf(stderr, " FXSR ....... %c\n", info->data.ia32.fxsr ? 'Y' : 'n'); | |||||
fprintf(stderr, " SSE ........ %c\n", info->data.ia32.sse ? 'Y' : 'n'); | |||||
fprintf(stderr, " SSE2 ....... %c\n", info->data.ia32.sse2 ? 'Y' : 'n'); | |||||
fprintf(stderr, " SSE3 ....... %c\n", info->data.ia32.sse3 ? 'Y' : 'n'); | |||||
fprintf(stderr, " SSSE3 ...... %c\n", info->data.ia32.ssse3 ? 'Y' : 'n'); | |||||
fprintf(stderr, " 3DNow! ..... %c\n", info->data.ia32._3dnow ? 'Y' : 'n'); | |||||
fprintf(stderr, " 3DNow!-ext . %c\n", info->data.ia32.ext3dnow? 'Y' : 'n'); | |||||
fprintf(stderr, " 3DNow!-MMX . %c\n", info->data.ia32.extmmx ? 'Y' : 'n'); | |||||
#endif | |||||
/* | |||||
* now have to check for OS support of SSE/SSE2 | |||||
*/ | |||||
if(info->data.ia32.fxsr || info->data.ia32.sse || info->data.ia32.sse2) { | |||||
#if defined FLAC__NO_SSE_OS | |||||
/* assume user knows better than us; turn it off */ | |||||
info->data.ia32.fxsr = info->data.ia32.sse = info->data.ia32.sse2 = info->data.ia32.sse3 = info->data.ia32.ssse3 = false; | |||||
#elif defined FLAC__SSE_OS | |||||
/* assume user knows better than us; leave as detected above */ | |||||
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) || defined(__APPLE__) | |||||
int sse = 0; | |||||
size_t len; | |||||
/* at least one of these must work: */ | |||||
len = sizeof(sse); sse = sse || (sysctlbyname("hw.instruction_sse", &sse, &len, NULL, 0) == 0 && sse); | |||||
len = sizeof(sse); sse = sse || (sysctlbyname("hw.optional.sse" , &sse, &len, NULL, 0) == 0 && sse); /* __APPLE__ ? */ | |||||
if(!sse) | |||||
info->data.ia32.fxsr = info->data.ia32.sse = info->data.ia32.sse2 = info->data.ia32.sse3 = info->data.ia32.ssse3 = false; | |||||
#elif defined(__NetBSD__) || defined (__OpenBSD__) | |||||
# if __NetBSD_Version__ >= 105250000 || (defined __OpenBSD__) | |||||
int val = 0, mib[2] = { CTL_MACHDEP, CPU_SSE }; | |||||
size_t len = sizeof(val); | |||||
if(sysctl(mib, 2, &val, &len, NULL, 0) < 0 || !val) | |||||
info->data.ia32.fxsr = info->data.ia32.sse = info->data.ia32.sse2 = info->data.ia32.sse3 = info->data.ia32.ssse3 = false; | |||||
else { /* double-check SSE2 */ | |||||
mib[1] = CPU_SSE2; | |||||
len = sizeof(val); | |||||
if(sysctl(mib, 2, &val, &len, NULL, 0) < 0 || !val) | |||||
info->data.ia32.sse2 = info->data.ia32.sse3 = info->data.ia32.ssse3 = false; | |||||
} | |||||
# else | |||||
info->data.ia32.fxsr = info->data.ia32.sse = info->data.ia32.sse2 = info->data.ia32.sse3 = info->data.ia32.ssse3 = false; | |||||
# endif | |||||
#elif defined(__linux__) | |||||
int sse = 0; | |||||
struct sigaction sigill_save; | |||||
#ifdef USE_OBSOLETE_SIGCONTEXT_FLAVOR | |||||
if(0 == sigaction(SIGILL, NULL, &sigill_save) && signal(SIGILL, (void (*)(int))sigill_handler_sse_os) != SIG_ERR) | |||||
#else | |||||
struct sigaction sigill_sse; | |||||
sigill_sse.sa_sigaction = sigill_handler_sse_os; | |||||
__sigemptyset(&sigill_sse.sa_mask); | |||||
sigill_sse.sa_flags = SA_SIGINFO | SA_RESETHAND; /* SA_RESETHAND just in case our SIGILL return jump breaks, so we don't get stuck in a loop */ | |||||
if(0 == sigaction(SIGILL, &sigill_sse, &sigill_save)) | |||||
#endif | |||||
{ | |||||
/* http://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html */ | |||||
/* see sigill_handler_sse_os() for an explanation of the following: */ | |||||
asm volatile ( | |||||
"xorl %0,%0\n\t" /* for some reason, still need to do this to clear 'sse' var */ | |||||
"xorps %%xmm0,%%xmm0\n\t" /* will cause SIGILL if unsupported by OS */ | |||||
"incl %0\n\t" /* SIGILL handler will jump over this */ | |||||
/* landing zone */ | |||||
"nop\n\t" /* SIGILL jump lands here if "inc" is 9 bytes */ | |||||
"nop\n\t" | |||||
"nop\n\t" | |||||
"nop\n\t" | |||||
"nop\n\t" | |||||
"nop\n\t" | |||||
"nop\n\t" /* SIGILL jump lands here if "inc" is 3 bytes (expected) */ | |||||
"nop\n\t" | |||||
"nop" /* SIGILL jump lands here if "inc" is 1 byte */ | |||||
: "=r"(sse) | |||||
: "r"(sse) | |||||
); | |||||
sigaction(SIGILL, &sigill_save, NULL); | |||||
} | |||||
if(!sse) | |||||
info->data.ia32.fxsr = info->data.ia32.sse = info->data.ia32.sse2 = info->data.ia32.sse3 = info->data.ia32.ssse3 = false; | |||||
#elif defined(_MSC_VER) | |||||
# ifdef USE_TRY_CATCH_FLAVOR | |||||
_try { | |||||
__asm { | |||||
# if _MSC_VER <= 1200 | |||||
/* VC6 assembler doesn't know SSE, have to emit bytecode instead */ | |||||
_emit 0x0F | |||||
_emit 0x57 | |||||
_emit 0xC0 | |||||
# else | |||||
xorps xmm0,xmm0 | |||||
# endif | |||||
} | |||||
} | |||||
_except(EXCEPTION_EXECUTE_HANDLER) { | |||||
if (_exception_code() == STATUS_ILLEGAL_INSTRUCTION) | |||||
info->data.ia32.fxsr = info->data.ia32.sse = info->data.ia32.sse2 = info->data.ia32.sse3 = info->data.ia32.ssse3 = false; | |||||
} | |||||
# else | |||||
int sse = 0; | |||||
LPTOP_LEVEL_EXCEPTION_FILTER save = SetUnhandledExceptionFilter(sigill_handler_sse_os); | |||||
/* see GCC version above for explanation */ | |||||
/* http://msdn2.microsoft.com/en-us/library/4ks26t93.aspx */ | |||||
/* http://www.codeproject.com/cpp/gccasm.asp */ | |||||
/* http://www.hick.org/~mmiller/msvc_inline_asm.html */ | |||||
__asm { | |||||
# if _MSC_VER <= 1200 | |||||
/* VC6 assembler doesn't know SSE, have to emit bytecode instead */ | |||||
_emit 0x0F | |||||
_emit 0x57 | |||||
_emit 0xC0 | |||||
# else | |||||
xorps xmm0,xmm0 | |||||
# endif | |||||
inc sse | |||||
nop | |||||
nop | |||||
nop | |||||
nop | |||||
nop | |||||
nop | |||||
nop | |||||
nop | |||||
nop | |||||
} | |||||
SetUnhandledExceptionFilter(save); | |||||
if(!sse) | |||||
info->data.ia32.fxsr = info->data.ia32.sse = info->data.ia32.sse2 = info->data.ia32.sse3 = info->data.ia32.ssse3 = false; | |||||
# endif | |||||
#else | |||||
/* no way to test, disable to be safe */ | |||||
info->data.ia32.fxsr = info->data.ia32.sse = info->data.ia32.sse2 = info->data.ia32.sse3 = info->data.ia32.ssse3 = false; | |||||
#endif | |||||
#ifdef DEBUG | |||||
fprintf(stderr, " SSE OS sup . %c\n", info->data.ia32.sse ? 'Y' : 'n'); | |||||
#endif | |||||
} | |||||
} | |||||
#else | |||||
info->use_asm = false; | |||||
#endif | |||||
/* | |||||
* PPC-specific | |||||
*/ | |||||
#elif defined FLAC__CPU_PPC | |||||
info->type = FLAC__CPUINFO_TYPE_PPC; | |||||
# if !defined FLAC__NO_ASM | |||||
info->use_asm = true; | |||||
# ifdef FLAC__USE_ALTIVEC | |||||
# if defined FLAC__SYS_DARWIN | |||||
{ | |||||
int val = 0, mib[2] = { CTL_HW, HW_VECTORUNIT }; | |||||
size_t len = sizeof(val); | |||||
info->data.ppc.altivec = !(sysctl(mib, 2, &val, &len, NULL, 0) || !val); | |||||
} | |||||
{ | |||||
host_basic_info_data_t hostInfo; | |||||
mach_msg_type_number_t infoCount; | |||||
infoCount = HOST_BASIC_INFO_COUNT; | |||||
host_info(mach_host_self(), HOST_BASIC_INFO, (host_info_t)&hostInfo, &infoCount); | |||||
info->data.ppc.ppc64 = (hostInfo.cpu_type == CPU_TYPE_POWERPC) && (hostInfo.cpu_subtype == CPU_SUBTYPE_POWERPC_970); | |||||
} | |||||
# else /* FLAC__USE_ALTIVEC && !FLAC__SYS_DARWIN */ | |||||
{ | |||||
/* no Darwin, do it the brute-force way */ | |||||
/* @@@@@@ this is not thread-safe; replace with SSE OS method above or remove */ | |||||
info->data.ppc.altivec = 0; | |||||
info->data.ppc.ppc64 = 0; | |||||
signal (SIGILL, sigill_handler); | |||||
canjump = 0; | |||||
if (!sigsetjmp (jmpbuf, 1)) { | |||||
canjump = 1; | |||||
asm volatile ( | |||||
"mtspr 256, %0\n\t" | |||||
"vand %%v0, %%v0, %%v0" | |||||
: | |||||
: "r" (-1) | |||||
); | |||||
info->data.ppc.altivec = 1; | |||||
} | |||||
canjump = 0; | |||||
if (!sigsetjmp (jmpbuf, 1)) { | |||||
int x = 0; | |||||
canjump = 1; | |||||
/* PPC64 hardware implements the cntlzd instruction */ | |||||
asm volatile ("cntlzd %0, %1" : "=r" (x) : "r" (x) ); | |||||
info->data.ppc.ppc64 = 1; | |||||
} | |||||
signal (SIGILL, SIG_DFL); /*@@@@@@ should save and restore old signal */ | |||||
} | |||||
# endif | |||||
# else /* !FLAC__USE_ALTIVEC */ | |||||
info->data.ppc.altivec = 0; | |||||
info->data.ppc.ppc64 = 0; | |||||
# endif | |||||
# else | |||||
info->use_asm = false; | |||||
# endif | |||||
/* | |||||
* unknown CPI | |||||
*/ | |||||
#else | |||||
info->type = FLAC__CPUINFO_TYPE_UNKNOWN; | |||||
info->use_asm = false; | |||||
#endif | |||||
} |
@@ -0,0 +1,142 @@ | |||||
/* libFLAC - Free Lossless Audio Codec library | |||||
* Copyright (C) 2000,2001,2002,2003,2004,2005,2006,2007 Josh Coalson | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions | |||||
* are met: | |||||
* | |||||
* - Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* | |||||
* - Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* | |||||
* - Neither the name of the Xiph.org Foundation nor the names of its | |||||
* contributors may be used to endorse or promote products derived from | |||||
* this software without specific prior written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR | |||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
*/ | |||||
#if HAVE_CONFIG_H | |||||
# include <config.h> | |||||
#endif | |||||
#include "include/private/crc.h" | |||||
/* CRC-8, poly = x^8 + x^2 + x^1 + x^0, init = 0 */ | |||||
FLAC__byte const FLAC__crc8_table[256] = { | |||||
0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, | |||||
0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, | |||||
0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, | |||||
0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, | |||||
0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, | |||||
0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, | |||||
0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, | |||||
0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, | |||||
0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, | |||||
0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, | |||||
0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, | |||||
0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, | |||||
0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, | |||||
0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, | |||||
0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, | |||||
0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, | |||||
0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, | |||||
0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, | |||||
0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, | |||||
0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, | |||||
0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, | |||||
0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, | |||||
0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, | |||||
0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, | |||||
0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, | |||||
0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, | |||||
0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, | |||||
0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, | |||||
0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, | |||||
0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, | |||||
0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, | |||||
0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3 | |||||
}; | |||||
/* CRC-16, poly = x^16 + x^15 + x^2 + x^0, init = 0 */ | |||||
unsigned FLAC__crc16_table[256] = { | |||||
0x0000, 0x8005, 0x800f, 0x000a, 0x801b, 0x001e, 0x0014, 0x8011, | |||||
0x8033, 0x0036, 0x003c, 0x8039, 0x0028, 0x802d, 0x8027, 0x0022, | |||||
0x8063, 0x0066, 0x006c, 0x8069, 0x0078, 0x807d, 0x8077, 0x0072, | |||||
0x0050, 0x8055, 0x805f, 0x005a, 0x804b, 0x004e, 0x0044, 0x8041, | |||||
0x80c3, 0x00c6, 0x00cc, 0x80c9, 0x00d8, 0x80dd, 0x80d7, 0x00d2, | |||||
0x00f0, 0x80f5, 0x80ff, 0x00fa, 0x80eb, 0x00ee, 0x00e4, 0x80e1, | |||||
0x00a0, 0x80a5, 0x80af, 0x00aa, 0x80bb, 0x00be, 0x00b4, 0x80b1, | |||||
0x8093, 0x0096, 0x009c, 0x8099, 0x0088, 0x808d, 0x8087, 0x0082, | |||||
0x8183, 0x0186, 0x018c, 0x8189, 0x0198, 0x819d, 0x8197, 0x0192, | |||||
0x01b0, 0x81b5, 0x81bf, 0x01ba, 0x81ab, 0x01ae, 0x01a4, 0x81a1, | |||||
0x01e0, 0x81e5, 0x81ef, 0x01ea, 0x81fb, 0x01fe, 0x01f4, 0x81f1, | |||||
0x81d3, 0x01d6, 0x01dc, 0x81d9, 0x01c8, 0x81cd, 0x81c7, 0x01c2, | |||||
0x0140, 0x8145, 0x814f, 0x014a, 0x815b, 0x015e, 0x0154, 0x8151, | |||||
0x8173, 0x0176, 0x017c, 0x8179, 0x0168, 0x816d, 0x8167, 0x0162, | |||||
0x8123, 0x0126, 0x012c, 0x8129, 0x0138, 0x813d, 0x8137, 0x0132, | |||||
0x0110, 0x8115, 0x811f, 0x011a, 0x810b, 0x010e, 0x0104, 0x8101, | |||||
0x8303, 0x0306, 0x030c, 0x8309, 0x0318, 0x831d, 0x8317, 0x0312, | |||||
0x0330, 0x8335, 0x833f, 0x033a, 0x832b, 0x032e, 0x0324, 0x8321, | |||||
0x0360, 0x8365, 0x836f, 0x036a, 0x837b, 0x037e, 0x0374, 0x8371, | |||||
0x8353, 0x0356, 0x035c, 0x8359, 0x0348, 0x834d, 0x8347, 0x0342, | |||||
0x03c0, 0x83c5, 0x83cf, 0x03ca, 0x83db, 0x03de, 0x03d4, 0x83d1, | |||||
0x83f3, 0x03f6, 0x03fc, 0x83f9, 0x03e8, 0x83ed, 0x83e7, 0x03e2, | |||||
0x83a3, 0x03a6, 0x03ac, 0x83a9, 0x03b8, 0x83bd, 0x83b7, 0x03b2, | |||||
0x0390, 0x8395, 0x839f, 0x039a, 0x838b, 0x038e, 0x0384, 0x8381, | |||||
0x0280, 0x8285, 0x828f, 0x028a, 0x829b, 0x029e, 0x0294, 0x8291, | |||||
0x82b3, 0x02b6, 0x02bc, 0x82b9, 0x02a8, 0x82ad, 0x82a7, 0x02a2, | |||||
0x82e3, 0x02e6, 0x02ec, 0x82e9, 0x02f8, 0x82fd, 0x82f7, 0x02f2, | |||||
0x02d0, 0x82d5, 0x82df, 0x02da, 0x82cb, 0x02ce, 0x02c4, 0x82c1, | |||||
0x8243, 0x0246, 0x024c, 0x8249, 0x0258, 0x825d, 0x8257, 0x0252, | |||||
0x0270, 0x8275, 0x827f, 0x027a, 0x826b, 0x026e, 0x0264, 0x8261, | |||||
0x0220, 0x8225, 0x822f, 0x022a, 0x823b, 0x023e, 0x0234, 0x8231, | |||||
0x8213, 0x0216, 0x021c, 0x8219, 0x0208, 0x820d, 0x8207, 0x0202 | |||||
}; | |||||
void FLAC__crc8_update(const FLAC__byte data, FLAC__uint8 *crc) | |||||
{ | |||||
*crc = FLAC__crc8_table[*crc ^ data]; | |||||
} | |||||
void FLAC__crc8_update_block(const FLAC__byte *data, unsigned len, FLAC__uint8 *crc) | |||||
{ | |||||
while(len--) | |||||
*crc = FLAC__crc8_table[*crc ^ *data++]; | |||||
} | |||||
FLAC__uint8 FLAC__crc8(const FLAC__byte *data, unsigned len) | |||||
{ | |||||
FLAC__uint8 crc = 0; | |||||
while(len--) | |||||
crc = FLAC__crc8_table[crc ^ *data++]; | |||||
return crc; | |||||
} | |||||
unsigned FLAC__crc16(const FLAC__byte *data, unsigned len) | |||||
{ | |||||
unsigned crc = 0; | |||||
while(len--) | |||||
crc = ((crc<<8) ^ FLAC__crc16_table[(crc>>8) ^ *data++]) & 0xffff; | |||||
return crc; | |||||
} |
@@ -0,0 +1,435 @@ | |||||
/* libFLAC - Free Lossless Audio Codec library | |||||
* Copyright (C) 2000,2001,2002,2003,2004,2005,2006,2007 Josh Coalson | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions | |||||
* are met: | |||||
* | |||||
* - Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* | |||||
* - Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* | |||||
* - Neither the name of the Xiph.org Foundation nor the names of its | |||||
* contributors may be used to endorse or promote products derived from | |||||
* this software without specific prior written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR | |||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
*/ | |||||
#if HAVE_CONFIG_H | |||||
# include <config.h> | |||||
#endif | |||||
#include <math.h> | |||||
#include <string.h> | |||||
#include "include/private/bitmath.h" | |||||
#include "include/private/fixed.h" | |||||
#include "../assert.h" | |||||
#ifndef M_LN2 | |||||
/* math.h in VC++ doesn't seem to have this (how Microsoft is that?) */ | |||||
#define M_LN2 0.69314718055994530942 | |||||
#endif | |||||
#ifdef min | |||||
#undef min | |||||
#endif | |||||
#define min(x,y) ((x) < (y)? (x) : (y)) | |||||
#ifdef local_abs | |||||
#undef local_abs | |||||
#endif | |||||
#define local_abs(x) ((unsigned)((x)<0? -(x) : (x))) | |||||
#ifdef FLAC__INTEGER_ONLY_LIBRARY | |||||
/* rbps stands for residual bits per sample | |||||
* | |||||
* (ln(2) * err) | |||||
* rbps = log (-----------) | |||||
* 2 ( n ) | |||||
*/ | |||||
static FLAC__fixedpoint local__compute_rbps_integerized(FLAC__uint32 err, FLAC__uint32 n) | |||||
{ | |||||
FLAC__uint32 rbps; | |||||
unsigned bits; /* the number of bits required to represent a number */ | |||||
int fracbits; /* the number of bits of rbps that comprise the fractional part */ | |||||
FLAC__ASSERT(sizeof(rbps) == sizeof(FLAC__fixedpoint)); | |||||
FLAC__ASSERT(err > 0); | |||||
FLAC__ASSERT(n > 0); | |||||
FLAC__ASSERT(n <= FLAC__MAX_BLOCK_SIZE); | |||||
if(err <= n) | |||||
return 0; | |||||
/* | |||||
* The above two things tell us 1) n fits in 16 bits; 2) err/n > 1. | |||||
* These allow us later to know we won't lose too much precision in the | |||||
* fixed-point division (err<<fracbits)/n. | |||||
*/ | |||||
fracbits = (8*sizeof(err)) - (FLAC__bitmath_ilog2(err)+1); | |||||
err <<= fracbits; | |||||
err /= n; | |||||
/* err now holds err/n with fracbits fractional bits */ | |||||
/* | |||||
* Whittle err down to 16 bits max. 16 significant bits is enough for | |||||
* our purposes. | |||||
*/ | |||||
FLAC__ASSERT(err > 0); | |||||
bits = FLAC__bitmath_ilog2(err)+1; | |||||
if(bits > 16) { | |||||
err >>= (bits-16); | |||||
fracbits -= (bits-16); | |||||
} | |||||
rbps = (FLAC__uint32)err; | |||||
/* Multiply by fixed-point version of ln(2), with 16 fractional bits */ | |||||
rbps *= FLAC__FP_LN2; | |||||
fracbits += 16; | |||||
FLAC__ASSERT(fracbits >= 0); | |||||
/* FLAC__fixedpoint_log2 requires fracbits%4 to be 0 */ | |||||
{ | |||||
const int f = fracbits & 3; | |||||
if(f) { | |||||
rbps >>= f; | |||||
fracbits -= f; | |||||
} | |||||
} | |||||
rbps = FLAC__fixedpoint_log2(rbps, fracbits, (unsigned)(-1)); | |||||
if(rbps == 0) | |||||
return 0; | |||||
/* | |||||
* The return value must have 16 fractional bits. Since the whole part | |||||
* of the base-2 log of a 32 bit number must fit in 5 bits, and fracbits | |||||
* must be >= -3, these assertion allows us to be able to shift rbps | |||||
* left if necessary to get 16 fracbits without losing any bits of the | |||||
* whole part of rbps. | |||||
* | |||||
* There is a slight chance due to accumulated error that the whole part | |||||
* will require 6 bits, so we use 6 in the assertion. Really though as | |||||
* long as it fits in 13 bits (32 - (16 - (-3))) we are fine. | |||||
*/ | |||||
FLAC__ASSERT((int)FLAC__bitmath_ilog2(rbps)+1 <= fracbits + 6); | |||||
FLAC__ASSERT(fracbits >= -3); | |||||
/* now shift the decimal point into place */ | |||||
if(fracbits < 16) | |||||
return rbps << (16-fracbits); | |||||
else if(fracbits > 16) | |||||
return rbps >> (fracbits-16); | |||||
else | |||||
return rbps; | |||||
} | |||||
static FLAC__fixedpoint local__compute_rbps_wide_integerized(FLAC__uint64 err, FLAC__uint32 n) | |||||
{ | |||||
FLAC__uint32 rbps; | |||||
unsigned bits; /* the number of bits required to represent a number */ | |||||
int fracbits; /* the number of bits of rbps that comprise the fractional part */ | |||||
FLAC__ASSERT(sizeof(rbps) == sizeof(FLAC__fixedpoint)); | |||||
FLAC__ASSERT(err > 0); | |||||
FLAC__ASSERT(n > 0); | |||||
FLAC__ASSERT(n <= FLAC__MAX_BLOCK_SIZE); | |||||
if(err <= n) | |||||
return 0; | |||||
/* | |||||
* The above two things tell us 1) n fits in 16 bits; 2) err/n > 1. | |||||
* These allow us later to know we won't lose too much precision in the | |||||
* fixed-point division (err<<fracbits)/n. | |||||
*/ | |||||
fracbits = (8*sizeof(err)) - (FLAC__bitmath_ilog2_wide(err)+1); | |||||
err <<= fracbits; | |||||
err /= n; | |||||
/* err now holds err/n with fracbits fractional bits */ | |||||
/* | |||||
* Whittle err down to 16 bits max. 16 significant bits is enough for | |||||
* our purposes. | |||||
*/ | |||||
FLAC__ASSERT(err > 0); | |||||
bits = FLAC__bitmath_ilog2_wide(err)+1; | |||||
if(bits > 16) { | |||||
err >>= (bits-16); | |||||
fracbits -= (bits-16); | |||||
} | |||||
rbps = (FLAC__uint32)err; | |||||
/* Multiply by fixed-point version of ln(2), with 16 fractional bits */ | |||||
rbps *= FLAC__FP_LN2; | |||||
fracbits += 16; | |||||
FLAC__ASSERT(fracbits >= 0); | |||||
/* FLAC__fixedpoint_log2 requires fracbits%4 to be 0 */ | |||||
{ | |||||
const int f = fracbits & 3; | |||||
if(f) { | |||||
rbps >>= f; | |||||
fracbits -= f; | |||||
} | |||||
} | |||||
rbps = FLAC__fixedpoint_log2(rbps, fracbits, (unsigned)(-1)); | |||||
if(rbps == 0) | |||||
return 0; | |||||
/* | |||||
* The return value must have 16 fractional bits. Since the whole part | |||||
* of the base-2 log of a 32 bit number must fit in 5 bits, and fracbits | |||||
* must be >= -3, these assertion allows us to be able to shift rbps | |||||
* left if necessary to get 16 fracbits without losing any bits of the | |||||
* whole part of rbps. | |||||
* | |||||
* There is a slight chance due to accumulated error that the whole part | |||||
* will require 6 bits, so we use 6 in the assertion. Really though as | |||||
* long as it fits in 13 bits (32 - (16 - (-3))) we are fine. | |||||
*/ | |||||
FLAC__ASSERT((int)FLAC__bitmath_ilog2(rbps)+1 <= fracbits + 6); | |||||
FLAC__ASSERT(fracbits >= -3); | |||||
/* now shift the decimal point into place */ | |||||
if(fracbits < 16) | |||||
return rbps << (16-fracbits); | |||||
else if(fracbits > 16) | |||||
return rbps >> (fracbits-16); | |||||
else | |||||
return rbps; | |||||
} | |||||
#endif | |||||
#ifndef FLAC__INTEGER_ONLY_LIBRARY | |||||
unsigned FLAC__fixed_compute_best_predictor(const FLAC__int32 data[], unsigned data_len, FLAC__float residual_bits_per_sample[FLAC__MAX_FIXED_ORDER+1]) | |||||
#else | |||||
unsigned FLAC__fixed_compute_best_predictor(const FLAC__int32 data[], unsigned data_len, FLAC__fixedpoint residual_bits_per_sample[FLAC__MAX_FIXED_ORDER+1]) | |||||
#endif | |||||
{ | |||||
FLAC__int32 last_error_0 = data[-1]; | |||||
FLAC__int32 last_error_1 = data[-1] - data[-2]; | |||||
FLAC__int32 last_error_2 = last_error_1 - (data[-2] - data[-3]); | |||||
FLAC__int32 last_error_3 = last_error_2 - (data[-2] - 2*data[-3] + data[-4]); | |||||
FLAC__int32 error, save; | |||||
FLAC__uint32 total_error_0 = 0, total_error_1 = 0, total_error_2 = 0, total_error_3 = 0, total_error_4 = 0; | |||||
unsigned i, order; | |||||
for(i = 0; i < data_len; i++) { | |||||
error = data[i] ; total_error_0 += local_abs(error); save = error; | |||||
error -= last_error_0; total_error_1 += local_abs(error); last_error_0 = save; save = error; | |||||
error -= last_error_1; total_error_2 += local_abs(error); last_error_1 = save; save = error; | |||||
error -= last_error_2; total_error_3 += local_abs(error); last_error_2 = save; save = error; | |||||
error -= last_error_3; total_error_4 += local_abs(error); last_error_3 = save; | |||||
} | |||||
if(total_error_0 < min(min(min(total_error_1, total_error_2), total_error_3), total_error_4)) | |||||
order = 0; | |||||
else if(total_error_1 < min(min(total_error_2, total_error_3), total_error_4)) | |||||
order = 1; | |||||
else if(total_error_2 < min(total_error_3, total_error_4)) | |||||
order = 2; | |||||
else if(total_error_3 < total_error_4) | |||||
order = 3; | |||||
else | |||||
order = 4; | |||||
/* Estimate the expected number of bits per residual signal sample. */ | |||||
/* 'total_error*' is linearly related to the variance of the residual */ | |||||
/* signal, so we use it directly to compute E(|x|) */ | |||||
FLAC__ASSERT(data_len > 0 || total_error_0 == 0); | |||||
FLAC__ASSERT(data_len > 0 || total_error_1 == 0); | |||||
FLAC__ASSERT(data_len > 0 || total_error_2 == 0); | |||||
FLAC__ASSERT(data_len > 0 || total_error_3 == 0); | |||||
FLAC__ASSERT(data_len > 0 || total_error_4 == 0); | |||||
#ifndef FLAC__INTEGER_ONLY_LIBRARY | |||||
residual_bits_per_sample[0] = (FLAC__float)((total_error_0 > 0) ? log(M_LN2 * (FLAC__double)total_error_0 / (FLAC__double)data_len) / M_LN2 : 0.0); | |||||
residual_bits_per_sample[1] = (FLAC__float)((total_error_1 > 0) ? log(M_LN2 * (FLAC__double)total_error_1 / (FLAC__double)data_len) / M_LN2 : 0.0); | |||||
residual_bits_per_sample[2] = (FLAC__float)((total_error_2 > 0) ? log(M_LN2 * (FLAC__double)total_error_2 / (FLAC__double)data_len) / M_LN2 : 0.0); | |||||
residual_bits_per_sample[3] = (FLAC__float)((total_error_3 > 0) ? log(M_LN2 * (FLAC__double)total_error_3 / (FLAC__double)data_len) / M_LN2 : 0.0); | |||||
residual_bits_per_sample[4] = (FLAC__float)((total_error_4 > 0) ? log(M_LN2 * (FLAC__double)total_error_4 / (FLAC__double)data_len) / M_LN2 : 0.0); | |||||
#else | |||||
residual_bits_per_sample[0] = (total_error_0 > 0) ? local__compute_rbps_integerized(total_error_0, data_len) : 0; | |||||
residual_bits_per_sample[1] = (total_error_1 > 0) ? local__compute_rbps_integerized(total_error_1, data_len) : 0; | |||||
residual_bits_per_sample[2] = (total_error_2 > 0) ? local__compute_rbps_integerized(total_error_2, data_len) : 0; | |||||
residual_bits_per_sample[3] = (total_error_3 > 0) ? local__compute_rbps_integerized(total_error_3, data_len) : 0; | |||||
residual_bits_per_sample[4] = (total_error_4 > 0) ? local__compute_rbps_integerized(total_error_4, data_len) : 0; | |||||
#endif | |||||
return order; | |||||
} | |||||
#ifndef FLAC__INTEGER_ONLY_LIBRARY | |||||
unsigned FLAC__fixed_compute_best_predictor_wide(const FLAC__int32 data[], unsigned data_len, FLAC__float residual_bits_per_sample[FLAC__MAX_FIXED_ORDER+1]) | |||||
#else | |||||
unsigned FLAC__fixed_compute_best_predictor_wide(const FLAC__int32 data[], unsigned data_len, FLAC__fixedpoint residual_bits_per_sample[FLAC__MAX_FIXED_ORDER+1]) | |||||
#endif | |||||
{ | |||||
FLAC__int32 last_error_0 = data[-1]; | |||||
FLAC__int32 last_error_1 = data[-1] - data[-2]; | |||||
FLAC__int32 last_error_2 = last_error_1 - (data[-2] - data[-3]); | |||||
FLAC__int32 last_error_3 = last_error_2 - (data[-2] - 2*data[-3] + data[-4]); | |||||
FLAC__int32 error, save; | |||||
/* total_error_* are 64-bits to avoid overflow when encoding | |||||
* erratic signals when the bits-per-sample and blocksize are | |||||
* large. | |||||
*/ | |||||
FLAC__uint64 total_error_0 = 0, total_error_1 = 0, total_error_2 = 0, total_error_3 = 0, total_error_4 = 0; | |||||
unsigned i, order; | |||||
for(i = 0; i < data_len; i++) { | |||||
error = data[i] ; total_error_0 += local_abs(error); save = error; | |||||
error -= last_error_0; total_error_1 += local_abs(error); last_error_0 = save; save = error; | |||||
error -= last_error_1; total_error_2 += local_abs(error); last_error_1 = save; save = error; | |||||
error -= last_error_2; total_error_3 += local_abs(error); last_error_2 = save; save = error; | |||||
error -= last_error_3; total_error_4 += local_abs(error); last_error_3 = save; | |||||
} | |||||
if(total_error_0 < min(min(min(total_error_1, total_error_2), total_error_3), total_error_4)) | |||||
order = 0; | |||||
else if(total_error_1 < min(min(total_error_2, total_error_3), total_error_4)) | |||||
order = 1; | |||||
else if(total_error_2 < min(total_error_3, total_error_4)) | |||||
order = 2; | |||||
else if(total_error_3 < total_error_4) | |||||
order = 3; | |||||
else | |||||
order = 4; | |||||
/* Estimate the expected number of bits per residual signal sample. */ | |||||
/* 'total_error*' is linearly related to the variance of the residual */ | |||||
/* signal, so we use it directly to compute E(|x|) */ | |||||
FLAC__ASSERT(data_len > 0 || total_error_0 == 0); | |||||
FLAC__ASSERT(data_len > 0 || total_error_1 == 0); | |||||
FLAC__ASSERT(data_len > 0 || total_error_2 == 0); | |||||
FLAC__ASSERT(data_len > 0 || total_error_3 == 0); | |||||
FLAC__ASSERT(data_len > 0 || total_error_4 == 0); | |||||
#ifndef FLAC__INTEGER_ONLY_LIBRARY | |||||
#if defined _MSC_VER || defined __MINGW32__ | |||||
/* with MSVC you have to spoon feed it the casting */ | |||||
residual_bits_per_sample[0] = (FLAC__float)((total_error_0 > 0) ? log(M_LN2 * (FLAC__double)(FLAC__int64)total_error_0 / (FLAC__double)data_len) / M_LN2 : 0.0); | |||||
residual_bits_per_sample[1] = (FLAC__float)((total_error_1 > 0) ? log(M_LN2 * (FLAC__double)(FLAC__int64)total_error_1 / (FLAC__double)data_len) / M_LN2 : 0.0); | |||||
residual_bits_per_sample[2] = (FLAC__float)((total_error_2 > 0) ? log(M_LN2 * (FLAC__double)(FLAC__int64)total_error_2 / (FLAC__double)data_len) / M_LN2 : 0.0); | |||||
residual_bits_per_sample[3] = (FLAC__float)((total_error_3 > 0) ? log(M_LN2 * (FLAC__double)(FLAC__int64)total_error_3 / (FLAC__double)data_len) / M_LN2 : 0.0); | |||||
residual_bits_per_sample[4] = (FLAC__float)((total_error_4 > 0) ? log(M_LN2 * (FLAC__double)(FLAC__int64)total_error_4 / (FLAC__double)data_len) / M_LN2 : 0.0); | |||||
#else | |||||
residual_bits_per_sample[0] = (FLAC__float)((total_error_0 > 0) ? log(M_LN2 * (FLAC__double)total_error_0 / (FLAC__double)data_len) / M_LN2 : 0.0); | |||||
residual_bits_per_sample[1] = (FLAC__float)((total_error_1 > 0) ? log(M_LN2 * (FLAC__double)total_error_1 / (FLAC__double)data_len) / M_LN2 : 0.0); | |||||
residual_bits_per_sample[2] = (FLAC__float)((total_error_2 > 0) ? log(M_LN2 * (FLAC__double)total_error_2 / (FLAC__double)data_len) / M_LN2 : 0.0); | |||||
residual_bits_per_sample[3] = (FLAC__float)((total_error_3 > 0) ? log(M_LN2 * (FLAC__double)total_error_3 / (FLAC__double)data_len) / M_LN2 : 0.0); | |||||
residual_bits_per_sample[4] = (FLAC__float)((total_error_4 > 0) ? log(M_LN2 * (FLAC__double)total_error_4 / (FLAC__double)data_len) / M_LN2 : 0.0); | |||||
#endif | |||||
#else | |||||
residual_bits_per_sample[0] = (total_error_0 > 0) ? local__compute_rbps_wide_integerized(total_error_0, data_len) : 0; | |||||
residual_bits_per_sample[1] = (total_error_1 > 0) ? local__compute_rbps_wide_integerized(total_error_1, data_len) : 0; | |||||
residual_bits_per_sample[2] = (total_error_2 > 0) ? local__compute_rbps_wide_integerized(total_error_2, data_len) : 0; | |||||
residual_bits_per_sample[3] = (total_error_3 > 0) ? local__compute_rbps_wide_integerized(total_error_3, data_len) : 0; | |||||
residual_bits_per_sample[4] = (total_error_4 > 0) ? local__compute_rbps_wide_integerized(total_error_4, data_len) : 0; | |||||
#endif | |||||
return order; | |||||
} | |||||
void FLAC__fixed_compute_residual(const FLAC__int32 data[], unsigned data_len, unsigned order, FLAC__int32 residual[]) | |||||
{ | |||||
const int idata_len = (int)data_len; | |||||
int i; | |||||
switch(order) { | |||||
case 0: | |||||
FLAC__ASSERT(sizeof(residual[0]) == sizeof(data[0])); | |||||
memcpy(residual, data, sizeof(residual[0])*data_len); | |||||
break; | |||||
case 1: | |||||
for(i = 0; i < idata_len; i++) | |||||
residual[i] = data[i] - data[i-1]; | |||||
break; | |||||
case 2: | |||||
for(i = 0; i < idata_len; i++) | |||||
#if 1 /* OPT: may be faster with some compilers on some systems */ | |||||
residual[i] = data[i] - (data[i-1] << 1) + data[i-2]; | |||||
#else | |||||
residual[i] = data[i] - 2*data[i-1] + data[i-2]; | |||||
#endif | |||||
break; | |||||
case 3: | |||||
for(i = 0; i < idata_len; i++) | |||||
#if 1 /* OPT: may be faster with some compilers on some systems */ | |||||
residual[i] = data[i] - (((data[i-1]-data[i-2])<<1) + (data[i-1]-data[i-2])) - data[i-3]; | |||||
#else | |||||
residual[i] = data[i] - 3*data[i-1] + 3*data[i-2] - data[i-3]; | |||||
#endif | |||||
break; | |||||
case 4: | |||||
for(i = 0; i < idata_len; i++) | |||||
#if 1 /* OPT: may be faster with some compilers on some systems */ | |||||
residual[i] = data[i] - ((data[i-1]+data[i-3])<<2) + ((data[i-2]<<2) + (data[i-2]<<1)) + data[i-4]; | |||||
#else | |||||
residual[i] = data[i] - 4*data[i-1] + 6*data[i-2] - 4*data[i-3] + data[i-4]; | |||||
#endif | |||||
break; | |||||
default: | |||||
FLAC__ASSERT(0); | |||||
} | |||||
} | |||||
void FLAC__fixed_restore_signal(const FLAC__int32 residual[], unsigned data_len, unsigned order, FLAC__int32 data[]) | |||||
{ | |||||
int i, idata_len = (int)data_len; | |||||
switch(order) { | |||||
case 0: | |||||
FLAC__ASSERT(sizeof(residual[0]) == sizeof(data[0])); | |||||
memcpy(data, residual, sizeof(residual[0])*data_len); | |||||
break; | |||||
case 1: | |||||
for(i = 0; i < idata_len; i++) | |||||
data[i] = residual[i] + data[i-1]; | |||||
break; | |||||
case 2: | |||||
for(i = 0; i < idata_len; i++) | |||||
#if 1 /* OPT: may be faster with some compilers on some systems */ | |||||
data[i] = residual[i] + (data[i-1]<<1) - data[i-2]; | |||||
#else | |||||
data[i] = residual[i] + 2*data[i-1] - data[i-2]; | |||||
#endif | |||||
break; | |||||
case 3: | |||||
for(i = 0; i < idata_len; i++) | |||||
#if 1 /* OPT: may be faster with some compilers on some systems */ | |||||
data[i] = residual[i] + (((data[i-1]-data[i-2])<<1) + (data[i-1]-data[i-2])) + data[i-3]; | |||||
#else | |||||
data[i] = residual[i] + 3*data[i-1] - 3*data[i-2] + data[i-3]; | |||||
#endif | |||||
break; | |||||
case 4: | |||||
for(i = 0; i < idata_len; i++) | |||||
#if 1 /* OPT: may be faster with some compilers on some systems */ | |||||
data[i] = residual[i] + ((data[i-1]+data[i-3])<<2) - ((data[i-2]<<2) + (data[i-2]<<1)) - data[i-4]; | |||||
#else | |||||
data[i] = residual[i] + 4*data[i-1] - 6*data[i-2] + 4*data[i-3] - data[i-4]; | |||||
#endif | |||||
break; | |||||
default: | |||||
FLAC__ASSERT(0); | |||||
} | |||||
} |
@@ -0,0 +1,308 @@ | |||||
/* libFLAC - Free Lossless Audio Codec library | |||||
* Copyright (C) 2004,2005,2006,2007 Josh Coalson | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions | |||||
* are met: | |||||
* | |||||
* - Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* | |||||
* - Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* | |||||
* - Neither the name of the Xiph.org Foundation nor the names of its | |||||
* contributors may be used to endorse or promote products derived from | |||||
* this software without specific prior written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR | |||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
*/ | |||||
#if HAVE_CONFIG_H | |||||
# include <config.h> | |||||
#endif | |||||
#include "../assert.h" | |||||
#include "include/private/float.h" | |||||
#ifdef FLAC__INTEGER_ONLY_LIBRARY | |||||
/* adjust for compilers that can't understand using LLU suffix for uint64_t literals */ | |||||
#ifdef _MSC_VER | |||||
#define FLAC__U64L(x) x | |||||
#else | |||||
#define FLAC__U64L(x) x##LLU | |||||
#endif | |||||
const FLAC__fixedpoint FLAC__FP_ZERO = 0; | |||||
const FLAC__fixedpoint FLAC__FP_ONE_HALF = 0x00008000; | |||||
const FLAC__fixedpoint FLAC__FP_ONE = 0x00010000; | |||||
const FLAC__fixedpoint FLAC__FP_LN2 = 45426; | |||||
const FLAC__fixedpoint FLAC__FP_E = 178145; | |||||
/* Lookup tables for Knuth's logarithm algorithm */ | |||||
#define LOG2_LOOKUP_PRECISION 16 | |||||
static const FLAC__uint32 log2_lookup[][LOG2_LOOKUP_PRECISION] = { | |||||
{ | |||||
/* | |||||
* 0 fraction bits | |||||
*/ | |||||
/* undefined */ 0x00000000, | |||||
/* lg(2/1) = */ 0x00000001, | |||||
/* lg(4/3) = */ 0x00000000, | |||||
/* lg(8/7) = */ 0x00000000, | |||||
/* lg(16/15) = */ 0x00000000, | |||||
/* lg(32/31) = */ 0x00000000, | |||||
/* lg(64/63) = */ 0x00000000, | |||||
/* lg(128/127) = */ 0x00000000, | |||||
/* lg(256/255) = */ 0x00000000, | |||||
/* lg(512/511) = */ 0x00000000, | |||||
/* lg(1024/1023) = */ 0x00000000, | |||||
/* lg(2048/2047) = */ 0x00000000, | |||||
/* lg(4096/4095) = */ 0x00000000, | |||||
/* lg(8192/8191) = */ 0x00000000, | |||||
/* lg(16384/16383) = */ 0x00000000, | |||||
/* lg(32768/32767) = */ 0x00000000 | |||||
}, | |||||
{ | |||||
/* | |||||
* 4 fraction bits | |||||
*/ | |||||
/* undefined */ 0x00000000, | |||||
/* lg(2/1) = */ 0x00000010, | |||||
/* lg(4/3) = */ 0x00000007, | |||||
/* lg(8/7) = */ 0x00000003, | |||||
/* lg(16/15) = */ 0x00000001, | |||||
/* lg(32/31) = */ 0x00000001, | |||||
/* lg(64/63) = */ 0x00000000, | |||||
/* lg(128/127) = */ 0x00000000, | |||||
/* lg(256/255) = */ 0x00000000, | |||||
/* lg(512/511) = */ 0x00000000, | |||||
/* lg(1024/1023) = */ 0x00000000, | |||||
/* lg(2048/2047) = */ 0x00000000, | |||||
/* lg(4096/4095) = */ 0x00000000, | |||||
/* lg(8192/8191) = */ 0x00000000, | |||||
/* lg(16384/16383) = */ 0x00000000, | |||||
/* lg(32768/32767) = */ 0x00000000 | |||||
}, | |||||
{ | |||||
/* | |||||
* 8 fraction bits | |||||
*/ | |||||
/* undefined */ 0x00000000, | |||||
/* lg(2/1) = */ 0x00000100, | |||||
/* lg(4/3) = */ 0x0000006a, | |||||
/* lg(8/7) = */ 0x00000031, | |||||
/* lg(16/15) = */ 0x00000018, | |||||
/* lg(32/31) = */ 0x0000000c, | |||||
/* lg(64/63) = */ 0x00000006, | |||||
/* lg(128/127) = */ 0x00000003, | |||||
/* lg(256/255) = */ 0x00000001, | |||||
/* lg(512/511) = */ 0x00000001, | |||||
/* lg(1024/1023) = */ 0x00000000, | |||||
/* lg(2048/2047) = */ 0x00000000, | |||||
/* lg(4096/4095) = */ 0x00000000, | |||||
/* lg(8192/8191) = */ 0x00000000, | |||||
/* lg(16384/16383) = */ 0x00000000, | |||||
/* lg(32768/32767) = */ 0x00000000 | |||||
}, | |||||
{ | |||||
/* | |||||
* 12 fraction bits | |||||
*/ | |||||
/* undefined */ 0x00000000, | |||||
/* lg(2/1) = */ 0x00001000, | |||||
/* lg(4/3) = */ 0x000006a4, | |||||
/* lg(8/7) = */ 0x00000315, | |||||
/* lg(16/15) = */ 0x0000017d, | |||||
/* lg(32/31) = */ 0x000000bc, | |||||
/* lg(64/63) = */ 0x0000005d, | |||||
/* lg(128/127) = */ 0x0000002e, | |||||
/* lg(256/255) = */ 0x00000017, | |||||
/* lg(512/511) = */ 0x0000000c, | |||||
/* lg(1024/1023) = */ 0x00000006, | |||||
/* lg(2048/2047) = */ 0x00000003, | |||||
/* lg(4096/4095) = */ 0x00000001, | |||||
/* lg(8192/8191) = */ 0x00000001, | |||||
/* lg(16384/16383) = */ 0x00000000, | |||||
/* lg(32768/32767) = */ 0x00000000 | |||||
}, | |||||
{ | |||||
/* | |||||
* 16 fraction bits | |||||
*/ | |||||
/* undefined */ 0x00000000, | |||||
/* lg(2/1) = */ 0x00010000, | |||||
/* lg(4/3) = */ 0x00006a40, | |||||
/* lg(8/7) = */ 0x00003151, | |||||
/* lg(16/15) = */ 0x000017d6, | |||||
/* lg(32/31) = */ 0x00000bba, | |||||
/* lg(64/63) = */ 0x000005d1, | |||||
/* lg(128/127) = */ 0x000002e6, | |||||
/* lg(256/255) = */ 0x00000172, | |||||
/* lg(512/511) = */ 0x000000b9, | |||||
/* lg(1024/1023) = */ 0x0000005c, | |||||
/* lg(2048/2047) = */ 0x0000002e, | |||||
/* lg(4096/4095) = */ 0x00000017, | |||||
/* lg(8192/8191) = */ 0x0000000c, | |||||
/* lg(16384/16383) = */ 0x00000006, | |||||
/* lg(32768/32767) = */ 0x00000003 | |||||
}, | |||||
{ | |||||
/* | |||||
* 20 fraction bits | |||||
*/ | |||||
/* undefined */ 0x00000000, | |||||
/* lg(2/1) = */ 0x00100000, | |||||
/* lg(4/3) = */ 0x0006a3fe, | |||||
/* lg(8/7) = */ 0x00031513, | |||||
/* lg(16/15) = */ 0x00017d60, | |||||
/* lg(32/31) = */ 0x0000bb9d, | |||||
/* lg(64/63) = */ 0x00005d10, | |||||
/* lg(128/127) = */ 0x00002e59, | |||||
/* lg(256/255) = */ 0x00001721, | |||||
/* lg(512/511) = */ 0x00000b8e, | |||||
/* lg(1024/1023) = */ 0x000005c6, | |||||
/* lg(2048/2047) = */ 0x000002e3, | |||||
/* lg(4096/4095) = */ 0x00000171, | |||||
/* lg(8192/8191) = */ 0x000000b9, | |||||
/* lg(16384/16383) = */ 0x0000005c, | |||||
/* lg(32768/32767) = */ 0x0000002e | |||||
}, | |||||
{ | |||||
/* | |||||
* 24 fraction bits | |||||
*/ | |||||
/* undefined */ 0x00000000, | |||||
/* lg(2/1) = */ 0x01000000, | |||||
/* lg(4/3) = */ 0x006a3fe6, | |||||
/* lg(8/7) = */ 0x00315130, | |||||
/* lg(16/15) = */ 0x0017d605, | |||||
/* lg(32/31) = */ 0x000bb9ca, | |||||
/* lg(64/63) = */ 0x0005d0fc, | |||||
/* lg(128/127) = */ 0x0002e58f, | |||||
/* lg(256/255) = */ 0x0001720e, | |||||
/* lg(512/511) = */ 0x0000b8d8, | |||||
/* lg(1024/1023) = */ 0x00005c61, | |||||
/* lg(2048/2047) = */ 0x00002e2d, | |||||
/* lg(4096/4095) = */ 0x00001716, | |||||
/* lg(8192/8191) = */ 0x00000b8b, | |||||
/* lg(16384/16383) = */ 0x000005c5, | |||||
/* lg(32768/32767) = */ 0x000002e3 | |||||
}, | |||||
{ | |||||
/* | |||||
* 28 fraction bits | |||||
*/ | |||||
/* undefined */ 0x00000000, | |||||
/* lg(2/1) = */ 0x10000000, | |||||
/* lg(4/3) = */ 0x06a3fe5c, | |||||
/* lg(8/7) = */ 0x03151301, | |||||
/* lg(16/15) = */ 0x017d6049, | |||||
/* lg(32/31) = */ 0x00bb9ca6, | |||||
/* lg(64/63) = */ 0x005d0fba, | |||||
/* lg(128/127) = */ 0x002e58f7, | |||||
/* lg(256/255) = */ 0x001720da, | |||||
/* lg(512/511) = */ 0x000b8d87, | |||||
/* lg(1024/1023) = */ 0x0005c60b, | |||||
/* lg(2048/2047) = */ 0x0002e2d7, | |||||
/* lg(4096/4095) = */ 0x00017160, | |||||
/* lg(8192/8191) = */ 0x0000b8ad, | |||||
/* lg(16384/16383) = */ 0x00005c56, | |||||
/* lg(32768/32767) = */ 0x00002e2b | |||||
} | |||||
}; | |||||
#if 0 | |||||
static const FLAC__uint64 log2_lookup_wide[] = { | |||||
{ | |||||
/* | |||||
* 32 fraction bits | |||||
*/ | |||||
/* undefined */ 0x00000000, | |||||
/* lg(2/1) = */ FLAC__U64L(0x100000000), | |||||
/* lg(4/3) = */ FLAC__U64L(0x6a3fe5c6), | |||||
/* lg(8/7) = */ FLAC__U64L(0x31513015), | |||||
/* lg(16/15) = */ FLAC__U64L(0x17d60497), | |||||
/* lg(32/31) = */ FLAC__U64L(0x0bb9ca65), | |||||
/* lg(64/63) = */ FLAC__U64L(0x05d0fba2), | |||||
/* lg(128/127) = */ FLAC__U64L(0x02e58f74), | |||||
/* lg(256/255) = */ FLAC__U64L(0x01720d9c), | |||||
/* lg(512/511) = */ FLAC__U64L(0x00b8d875), | |||||
/* lg(1024/1023) = */ FLAC__U64L(0x005c60aa), | |||||
/* lg(2048/2047) = */ FLAC__U64L(0x002e2d72), | |||||
/* lg(4096/4095) = */ FLAC__U64L(0x00171600), | |||||
/* lg(8192/8191) = */ FLAC__U64L(0x000b8ad2), | |||||
/* lg(16384/16383) = */ FLAC__U64L(0x0005c55d), | |||||
/* lg(32768/32767) = */ FLAC__U64L(0x0002e2ac) | |||||
}, | |||||
{ | |||||
/* | |||||
* 48 fraction bits | |||||
*/ | |||||
/* undefined */ 0x00000000, | |||||
/* lg(2/1) = */ FLAC__U64L(0x1000000000000), | |||||
/* lg(4/3) = */ FLAC__U64L(0x6a3fe5c60429), | |||||
/* lg(8/7) = */ FLAC__U64L(0x315130157f7a), | |||||
/* lg(16/15) = */ FLAC__U64L(0x17d60496cfbb), | |||||
/* lg(32/31) = */ FLAC__U64L(0xbb9ca64ecac), | |||||
/* lg(64/63) = */ FLAC__U64L(0x5d0fba187cd), | |||||
/* lg(128/127) = */ FLAC__U64L(0x2e58f7441ee), | |||||
/* lg(256/255) = */ FLAC__U64L(0x1720d9c06a8), | |||||
/* lg(512/511) = */ FLAC__U64L(0xb8d8752173), | |||||
/* lg(1024/1023) = */ FLAC__U64L(0x5c60aa252e), | |||||
/* lg(2048/2047) = */ FLAC__U64L(0x2e2d71b0d8), | |||||
/* lg(4096/4095) = */ FLAC__U64L(0x1716001719), | |||||
/* lg(8192/8191) = */ FLAC__U64L(0xb8ad1de1b), | |||||
/* lg(16384/16383) = */ FLAC__U64L(0x5c55d640d), | |||||
/* lg(32768/32767) = */ FLAC__U64L(0x2e2abcf52) | |||||
} | |||||
}; | |||||
#endif | |||||
FLAC__uint32 FLAC__fixedpoint_log2(FLAC__uint32 x, unsigned fracbits, unsigned precision) | |||||
{ | |||||
const FLAC__uint32 ONE = (1u << fracbits); | |||||
const FLAC__uint32 *table = log2_lookup[fracbits >> 2]; | |||||
FLAC__ASSERT(fracbits < 32); | |||||
FLAC__ASSERT((fracbits & 0x3) == 0); | |||||
if(x < ONE) | |||||
return 0; | |||||
if(precision > LOG2_LOOKUP_PRECISION) | |||||
precision = LOG2_LOOKUP_PRECISION; | |||||
/* Knuth's algorithm for computing logarithms, optimized for base-2 with lookup tables */ | |||||
{ | |||||
FLAC__uint32 y = 0; | |||||
FLAC__uint32 z = x >> 1, k = 1; | |||||
while (x > ONE && k < precision) { | |||||
if (x - z >= ONE) { | |||||
x -= z; | |||||
z = x >> k; | |||||
y += table[k]; | |||||
} | |||||
else { | |||||
z >>= 1; | |||||
k++; | |||||
} | |||||
} | |||||
return y; | |||||
} | |||||
} | |||||
#endif /* defined FLAC__INTEGER_ONLY_LIBRARY */ |
@@ -0,0 +1,598 @@ | |||||
/* libFLAC - Free Lossless Audio Codec library | |||||
* Copyright (C) 2000,2001,2002,2003,2004,2005,2006,2007 Josh Coalson | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions | |||||
* are met: | |||||
* | |||||
* - Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* | |||||
* - Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* | |||||
* - Neither the name of the Xiph.org Foundation nor the names of its | |||||
* contributors may be used to endorse or promote products derived from | |||||
* this software without specific prior written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR | |||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
*/ | |||||
#if HAVE_CONFIG_H | |||||
# include <config.h> | |||||
#endif | |||||
#include <stdio.h> | |||||
#include <stdlib.h> /* for qsort() */ | |||||
#include <string.h> /* for memset() */ | |||||
#include "../assert.h" | |||||
#include "../format.h" | |||||
#include "include/private/format.h" | |||||
#ifndef FLaC__INLINE | |||||
#define FLaC__INLINE | |||||
#endif | |||||
#ifdef min | |||||
#undef min | |||||
#endif | |||||
#define min(a,b) ((a)<(b)?(a):(b)) | |||||
/* adjust for compilers that can't understand using LLU suffix for uint64_t literals */ | |||||
#ifdef _MSC_VER | |||||
#define FLAC__U64L(x) x | |||||
#else | |||||
#define FLAC__U64L(x) x##LLU | |||||
#endif | |||||
/* VERSION should come from configure */ | |||||
FLAC_API const char *FLAC__VERSION_STRING = VERSION | |||||
; | |||||
#if defined _MSC_VER || defined __BORLANDC__ || defined __MINW32__ | |||||
/* yet one more hack because of MSVC6: */ | |||||
FLAC_API const char *FLAC__VENDOR_STRING = "reference libFLAC 1.2.1 20070917"; | |||||
#else | |||||
FLAC_API const char *FLAC__VENDOR_STRING = "reference libFLAC " VERSION " 20070917"; | |||||
#endif | |||||
FLAC_API const FLAC__byte FLAC__STREAM_SYNC_STRING[4] = { 'f','L','a','C' }; | |||||
FLAC_API const unsigned FLAC__STREAM_SYNC = 0x664C6143; | |||||
FLAC_API const unsigned FLAC__STREAM_SYNC_LEN = 32; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_STREAMINFO_MIN_BLOCK_SIZE_LEN = 16; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_STREAMINFO_MAX_BLOCK_SIZE_LEN = 16; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_STREAMINFO_MIN_FRAME_SIZE_LEN = 24; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_STREAMINFO_MAX_FRAME_SIZE_LEN = 24; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_STREAMINFO_SAMPLE_RATE_LEN = 20; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_STREAMINFO_CHANNELS_LEN = 3; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_STREAMINFO_BITS_PER_SAMPLE_LEN = 5; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_STREAMINFO_TOTAL_SAMPLES_LEN = 36; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_STREAMINFO_MD5SUM_LEN = 128; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_APPLICATION_ID_LEN = 32; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_SEEKPOINT_SAMPLE_NUMBER_LEN = 64; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_SEEKPOINT_STREAM_OFFSET_LEN = 64; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_SEEKPOINT_FRAME_SAMPLES_LEN = 16; /* bits */ | |||||
FLAC_API const FLAC__uint64 FLAC__STREAM_METADATA_SEEKPOINT_PLACEHOLDER = FLAC__U64L(0xffffffffffffffff); | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN = 32; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_VORBIS_COMMENT_NUM_COMMENTS_LEN = 32; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_CUESHEET_INDEX_OFFSET_LEN = 64; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_CUESHEET_INDEX_NUMBER_LEN = 8; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_CUESHEET_INDEX_RESERVED_LEN = 3*8; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_CUESHEET_TRACK_OFFSET_LEN = 64; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_CUESHEET_TRACK_NUMBER_LEN = 8; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_CUESHEET_TRACK_ISRC_LEN = 12*8; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_CUESHEET_TRACK_TYPE_LEN = 1; /* bit */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_CUESHEET_TRACK_PRE_EMPHASIS_LEN = 1; /* bit */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_CUESHEET_TRACK_RESERVED_LEN = 6+13*8; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_CUESHEET_TRACK_NUM_INDICES_LEN = 8; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_CUESHEET_MEDIA_CATALOG_NUMBER_LEN = 128*8; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_CUESHEET_LEAD_IN_LEN = 64; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_CUESHEET_IS_CD_LEN = 1; /* bit */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_CUESHEET_RESERVED_LEN = 7+258*8; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_CUESHEET_NUM_TRACKS_LEN = 8; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_PICTURE_TYPE_LEN = 32; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_PICTURE_MIME_TYPE_LENGTH_LEN = 32; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_PICTURE_DESCRIPTION_LENGTH_LEN = 32; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_PICTURE_WIDTH_LEN = 32; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_PICTURE_HEIGHT_LEN = 32; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_PICTURE_DEPTH_LEN = 32; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_PICTURE_COLORS_LEN = 32; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_PICTURE_DATA_LENGTH_LEN = 32; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_IS_LAST_LEN = 1; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_TYPE_LEN = 7; /* bits */ | |||||
FLAC_API const unsigned FLAC__STREAM_METADATA_LENGTH_LEN = 24; /* bits */ | |||||
FLAC_API const unsigned FLAC__FRAME_HEADER_SYNC = 0x3ffe; | |||||
FLAC_API const unsigned FLAC__FRAME_HEADER_SYNC_LEN = 14; /* bits */ | |||||
FLAC_API const unsigned FLAC__FRAME_HEADER_RESERVED_LEN = 1; /* bits */ | |||||
FLAC_API const unsigned FLAC__FRAME_HEADER_BLOCKING_STRATEGY_LEN = 1; /* bits */ | |||||
FLAC_API const unsigned FLAC__FRAME_HEADER_BLOCK_SIZE_LEN = 4; /* bits */ | |||||
FLAC_API const unsigned FLAC__FRAME_HEADER_SAMPLE_RATE_LEN = 4; /* bits */ | |||||
FLAC_API const unsigned FLAC__FRAME_HEADER_CHANNEL_ASSIGNMENT_LEN = 4; /* bits */ | |||||
FLAC_API const unsigned FLAC__FRAME_HEADER_BITS_PER_SAMPLE_LEN = 3; /* bits */ | |||||
FLAC_API const unsigned FLAC__FRAME_HEADER_ZERO_PAD_LEN = 1; /* bits */ | |||||
FLAC_API const unsigned FLAC__FRAME_HEADER_CRC_LEN = 8; /* bits */ | |||||
FLAC_API const unsigned FLAC__FRAME_FOOTER_CRC_LEN = 16; /* bits */ | |||||
FLAC_API const unsigned FLAC__ENTROPY_CODING_METHOD_TYPE_LEN = 2; /* bits */ | |||||
FLAC_API const unsigned FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE_ORDER_LEN = 4; /* bits */ | |||||
FLAC_API const unsigned FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE_PARAMETER_LEN = 4; /* bits */ | |||||
FLAC_API const unsigned FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE2_PARAMETER_LEN = 5; /* bits */ | |||||
FLAC_API const unsigned FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE_RAW_LEN = 5; /* bits */ | |||||
FLAC_API const unsigned FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE_ESCAPE_PARAMETER = 15; /* == (1<<FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE_PARAMETER_LEN)-1 */ | |||||
FLAC_API const unsigned FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE2_ESCAPE_PARAMETER = 31; /* == (1<<FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE2_PARAMETER_LEN)-1 */ | |||||
FLAC_API const char * const FLAC__EntropyCodingMethodTypeString[] = { | |||||
"PARTITIONED_RICE", | |||||
"PARTITIONED_RICE2" | |||||
}; | |||||
FLAC_API const unsigned FLAC__SUBFRAME_LPC_QLP_COEFF_PRECISION_LEN = 4; /* bits */ | |||||
FLAC_API const unsigned FLAC__SUBFRAME_LPC_QLP_SHIFT_LEN = 5; /* bits */ | |||||
FLAC_API const unsigned FLAC__SUBFRAME_ZERO_PAD_LEN = 1; /* bits */ | |||||
FLAC_API const unsigned FLAC__SUBFRAME_TYPE_LEN = 6; /* bits */ | |||||
FLAC_API const unsigned FLAC__SUBFRAME_WASTED_BITS_FLAG_LEN = 1; /* bits */ | |||||
FLAC_API const unsigned FLAC__SUBFRAME_TYPE_CONSTANT_BYTE_ALIGNED_MASK = 0x00; | |||||
FLAC_API const unsigned FLAC__SUBFRAME_TYPE_VERBATIM_BYTE_ALIGNED_MASK = 0x02; | |||||
FLAC_API const unsigned FLAC__SUBFRAME_TYPE_FIXED_BYTE_ALIGNED_MASK = 0x10; | |||||
FLAC_API const unsigned FLAC__SUBFRAME_TYPE_LPC_BYTE_ALIGNED_MASK = 0x40; | |||||
FLAC_API const char * const FLAC__SubframeTypeString[] = { | |||||
"CONSTANT", | |||||
"VERBATIM", | |||||
"FIXED", | |||||
"LPC" | |||||
}; | |||||
FLAC_API const char * const FLAC__ChannelAssignmentString[] = { | |||||
"INDEPENDENT", | |||||
"LEFT_SIDE", | |||||
"RIGHT_SIDE", | |||||
"MID_SIDE" | |||||
}; | |||||
FLAC_API const char * const FLAC__FrameNumberTypeString[] = { | |||||
"FRAME_NUMBER_TYPE_FRAME_NUMBER", | |||||
"FRAME_NUMBER_TYPE_SAMPLE_NUMBER" | |||||
}; | |||||
FLAC_API const char * const FLAC__MetadataTypeString[] = { | |||||
"STREAMINFO", | |||||
"PADDING", | |||||
"APPLICATION", | |||||
"SEEKTABLE", | |||||
"VORBIS_COMMENT", | |||||
"CUESHEET", | |||||
"PICTURE" | |||||
}; | |||||
FLAC_API const char * const FLAC__StreamMetadata_Picture_TypeString[] = { | |||||
"Other", | |||||
"32x32 pixels 'file icon' (PNG only)", | |||||
"Other file icon", | |||||
"Cover (front)", | |||||
"Cover (back)", | |||||
"Leaflet page", | |||||
"Media (e.g. label side of CD)", | |||||
"Lead artist/lead performer/soloist", | |||||
"Artist/performer", | |||||
"Conductor", | |||||
"Band/Orchestra", | |||||
"Composer", | |||||
"Lyricist/text writer", | |||||
"Recording Location", | |||||
"During recording", | |||||
"During performance", | |||||
"Movie/video screen capture", | |||||
"A bright coloured fish", | |||||
"Illustration", | |||||
"Band/artist logotype", | |||||
"Publisher/Studio logotype" | |||||
}; | |||||
FLAC_API FLAC__bool FLAC__format_sample_rate_is_valid(unsigned sample_rate) | |||||
{ | |||||
if(sample_rate == 0 || sample_rate > FLAC__MAX_SAMPLE_RATE) { | |||||
return false; | |||||
} | |||||
else | |||||
return true; | |||||
} | |||||
FLAC_API FLAC__bool FLAC__format_sample_rate_is_subset(unsigned sample_rate) | |||||
{ | |||||
if( | |||||
!FLAC__format_sample_rate_is_valid(sample_rate) || | |||||
( | |||||
sample_rate >= (1u << 16) && | |||||
!(sample_rate % 1000 == 0 || sample_rate % 10 == 0) | |||||
) | |||||
) { | |||||
return false; | |||||
} | |||||
else | |||||
return true; | |||||
} | |||||
/* @@@@ add to unit tests; it is already indirectly tested by the metadata_object tests */ | |||||
FLAC_API FLAC__bool FLAC__format_seektable_is_legal(const FLAC__StreamMetadata_SeekTable *seek_table) | |||||
{ | |||||
unsigned i; | |||||
FLAC__uint64 prev_sample_number = 0; | |||||
FLAC__bool got_prev = false; | |||||
FLAC__ASSERT(0 != seek_table); | |||||
for(i = 0; i < seek_table->num_points; i++) { | |||||
if(got_prev) { | |||||
if( | |||||
seek_table->points[i].sample_number != FLAC__STREAM_METADATA_SEEKPOINT_PLACEHOLDER && | |||||
seek_table->points[i].sample_number <= prev_sample_number | |||||
) | |||||
return false; | |||||
} | |||||
prev_sample_number = seek_table->points[i].sample_number; | |||||
got_prev = true; | |||||
} | |||||
return true; | |||||
} | |||||
/* used as the sort predicate for qsort() */ | |||||
static int JUCE_CDECL seekpoint_compare_(const FLAC__StreamMetadata_SeekPoint *l, const FLAC__StreamMetadata_SeekPoint *r) | |||||
{ | |||||
/* we don't just 'return l->sample_number - r->sample_number' since the result (FLAC__int64) might overflow an 'int' */ | |||||
if(l->sample_number == r->sample_number) | |||||
return 0; | |||||
else if(l->sample_number < r->sample_number) | |||||
return -1; | |||||
else | |||||
return 1; | |||||
} | |||||
/* @@@@ add to unit tests; it is already indirectly tested by the metadata_object tests */ | |||||
FLAC_API unsigned FLAC__format_seektable_sort(FLAC__StreamMetadata_SeekTable *seek_table) | |||||
{ | |||||
unsigned i, j; | |||||
FLAC__bool first; | |||||
FLAC__ASSERT(0 != seek_table); | |||||
/* sort the seekpoints */ | |||||
qsort(seek_table->points, seek_table->num_points, sizeof(FLAC__StreamMetadata_SeekPoint), (int (JUCE_CDECL *)(const void *, const void *))seekpoint_compare_); | |||||
/* uniquify the seekpoints */ | |||||
first = true; | |||||
for(i = j = 0; i < seek_table->num_points; i++) { | |||||
if(seek_table->points[i].sample_number != FLAC__STREAM_METADATA_SEEKPOINT_PLACEHOLDER) { | |||||
if(!first) { | |||||
if(seek_table->points[i].sample_number == seek_table->points[j-1].sample_number) | |||||
continue; | |||||
} | |||||
} | |||||
first = false; | |||||
seek_table->points[j++] = seek_table->points[i]; | |||||
} | |||||
for(i = j; i < seek_table->num_points; i++) { | |||||
seek_table->points[i].sample_number = FLAC__STREAM_METADATA_SEEKPOINT_PLACEHOLDER; | |||||
seek_table->points[i].stream_offset = 0; | |||||
seek_table->points[i].frame_samples = 0; | |||||
} | |||||
return j; | |||||
} | |||||
/* | |||||
* also disallows non-shortest-form encodings, c.f. | |||||
* http://www.unicode.org/versions/corrigendum1.html | |||||
* and a more clear explanation at the end of this section: | |||||
* http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 | |||||
*/ | |||||
static FLaC__INLINE unsigned utf8len_(const FLAC__byte *utf8) | |||||
{ | |||||
FLAC__ASSERT(0 != utf8); | |||||
if ((utf8[0] & 0x80) == 0) { | |||||
return 1; | |||||
} | |||||
else if ((utf8[0] & 0xE0) == 0xC0 && (utf8[1] & 0xC0) == 0x80) { | |||||
if ((utf8[0] & 0xFE) == 0xC0) /* overlong sequence check */ | |||||
return 0; | |||||
return 2; | |||||
} | |||||
else if ((utf8[0] & 0xF0) == 0xE0 && (utf8[1] & 0xC0) == 0x80 && (utf8[2] & 0xC0) == 0x80) { | |||||
if (utf8[0] == 0xE0 && (utf8[1] & 0xE0) == 0x80) /* overlong sequence check */ | |||||
return 0; | |||||
/* illegal surrogates check (U+D800...U+DFFF and U+FFFE...U+FFFF) */ | |||||
if (utf8[0] == 0xED && (utf8[1] & 0xE0) == 0xA0) /* D800-DFFF */ | |||||
return 0; | |||||
if (utf8[0] == 0xEF && utf8[1] == 0xBF && (utf8[2] & 0xFE) == 0xBE) /* FFFE-FFFF */ | |||||
return 0; | |||||
return 3; | |||||
} | |||||
else if ((utf8[0] & 0xF8) == 0xF0 && (utf8[1] & 0xC0) == 0x80 && (utf8[2] & 0xC0) == 0x80 && (utf8[3] & 0xC0) == 0x80) { | |||||
if (utf8[0] == 0xF0 && (utf8[1] & 0xF0) == 0x80) /* overlong sequence check */ | |||||
return 0; | |||||
return 4; | |||||
} | |||||
else if ((utf8[0] & 0xFC) == 0xF8 && (utf8[1] & 0xC0) == 0x80 && (utf8[2] & 0xC0) == 0x80 && (utf8[3] & 0xC0) == 0x80 && (utf8[4] & 0xC0) == 0x80) { | |||||
if (utf8[0] == 0xF8 && (utf8[1] & 0xF8) == 0x80) /* overlong sequence check */ | |||||
return 0; | |||||
return 5; | |||||
} | |||||
else if ((utf8[0] & 0xFE) == 0xFC && (utf8[1] & 0xC0) == 0x80 && (utf8[2] & 0xC0) == 0x80 && (utf8[3] & 0xC0) == 0x80 && (utf8[4] & 0xC0) == 0x80 && (utf8[5] & 0xC0) == 0x80) { | |||||
if (utf8[0] == 0xFC && (utf8[1] & 0xFC) == 0x80) /* overlong sequence check */ | |||||
return 0; | |||||
return 6; | |||||
} | |||||
else { | |||||
return 0; | |||||
} | |||||
} | |||||
FLAC_API FLAC__bool FLAC__format_vorbiscomment_entry_name_is_legal(const char *name) | |||||
{ | |||||
char c; | |||||
for(c = *name; c; c = *(++name)) | |||||
if(c < 0x20 || c == 0x3d || c > 0x7d) | |||||
return false; | |||||
return true; | |||||
} | |||||
FLAC_API FLAC__bool FLAC__format_vorbiscomment_entry_value_is_legal(const FLAC__byte *value, unsigned length) | |||||
{ | |||||
if(length == (unsigned)(-1)) { | |||||
while(*value) { | |||||
unsigned n = utf8len_(value); | |||||
if(n == 0) | |||||
return false; | |||||
value += n; | |||||
} | |||||
} | |||||
else { | |||||
const FLAC__byte *end = value + length; | |||||
while(value < end) { | |||||
unsigned n = utf8len_(value); | |||||
if(n == 0) | |||||
return false; | |||||
value += n; | |||||
} | |||||
if(value != end) | |||||
return false; | |||||
} | |||||
return true; | |||||
} | |||||
FLAC_API FLAC__bool FLAC__format_vorbiscomment_entry_is_legal(const FLAC__byte *entry, unsigned length) | |||||
{ | |||||
const FLAC__byte *s, *end; | |||||
for(s = entry, end = s + length; s < end && *s != '='; s++) { | |||||
if(*s < 0x20 || *s > 0x7D) | |||||
return false; | |||||
} | |||||
if(s == end) | |||||
return false; | |||||
s++; /* skip '=' */ | |||||
while(s < end) { | |||||
unsigned n = utf8len_(s); | |||||
if(n == 0) | |||||
return false; | |||||
s += n; | |||||
} | |||||
if(s != end) | |||||
return false; | |||||
return true; | |||||
} | |||||
/* @@@@ add to unit tests; it is already indirectly tested by the metadata_object tests */ | |||||
FLAC_API FLAC__bool FLAC__format_cuesheet_is_legal(const FLAC__StreamMetadata_CueSheet *cue_sheet, FLAC__bool check_cd_da_subset, const char **violation) | |||||
{ | |||||
unsigned i, j; | |||||
if(check_cd_da_subset) { | |||||
if(cue_sheet->lead_in < 2 * 44100) { | |||||
if(violation) *violation = "CD-DA cue sheet must have a lead-in length of at least 2 seconds"; | |||||
return false; | |||||
} | |||||
if(cue_sheet->lead_in % 588 != 0) { | |||||
if(violation) *violation = "CD-DA cue sheet lead-in length must be evenly divisible by 588 samples"; | |||||
return false; | |||||
} | |||||
} | |||||
if(cue_sheet->num_tracks == 0) { | |||||
if(violation) *violation = "cue sheet must have at least one track (the lead-out)"; | |||||
return false; | |||||
} | |||||
if(check_cd_da_subset && cue_sheet->tracks[cue_sheet->num_tracks-1].number != 170) { | |||||
if(violation) *violation = "CD-DA cue sheet must have a lead-out track number 170 (0xAA)"; | |||||
return false; | |||||
} | |||||
for(i = 0; i < cue_sheet->num_tracks; i++) { | |||||
if(cue_sheet->tracks[i].number == 0) { | |||||
if(violation) *violation = "cue sheet may not have a track number 0"; | |||||
return false; | |||||
} | |||||
if(check_cd_da_subset) { | |||||
if(!((cue_sheet->tracks[i].number >= 1 && cue_sheet->tracks[i].number <= 99) || cue_sheet->tracks[i].number == 170)) { | |||||
if(violation) *violation = "CD-DA cue sheet track number must be 1-99 or 170"; | |||||
return false; | |||||
} | |||||
} | |||||
if(check_cd_da_subset && cue_sheet->tracks[i].offset % 588 != 0) { | |||||
if(violation) { | |||||
if(i == cue_sheet->num_tracks-1) /* the lead-out track... */ | |||||
*violation = "CD-DA cue sheet lead-out offset must be evenly divisible by 588 samples"; | |||||
else | |||||
*violation = "CD-DA cue sheet track offset must be evenly divisible by 588 samples"; | |||||
} | |||||
return false; | |||||
} | |||||
if(i < cue_sheet->num_tracks - 1) { | |||||
if(cue_sheet->tracks[i].num_indices == 0) { | |||||
if(violation) *violation = "cue sheet track must have at least one index point"; | |||||
return false; | |||||
} | |||||
if(cue_sheet->tracks[i].indices[0].number > 1) { | |||||
if(violation) *violation = "cue sheet track's first index number must be 0 or 1"; | |||||
return false; | |||||
} | |||||
} | |||||
for(j = 0; j < cue_sheet->tracks[i].num_indices; j++) { | |||||
if(check_cd_da_subset && cue_sheet->tracks[i].indices[j].offset % 588 != 0) { | |||||
if(violation) *violation = "CD-DA cue sheet track index offset must be evenly divisible by 588 samples"; | |||||
return false; | |||||
} | |||||
if(j > 0) { | |||||
if(cue_sheet->tracks[i].indices[j].number != cue_sheet->tracks[i].indices[j-1].number + 1) { | |||||
if(violation) *violation = "cue sheet track index numbers must increase by 1"; | |||||
return false; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
/* @@@@ add to unit tests; it is already indirectly tested by the metadata_object tests */ | |||||
FLAC_API FLAC__bool FLAC__format_picture_is_legal(const FLAC__StreamMetadata_Picture *picture, const char **violation) | |||||
{ | |||||
char *p; | |||||
FLAC__byte *b; | |||||
for(p = picture->mime_type; *p; p++) { | |||||
if(*p < 0x20 || *p > 0x7e) { | |||||
if(violation) *violation = "MIME type string must contain only printable ASCII characters (0x20-0x7e)"; | |||||
return false; | |||||
} | |||||
} | |||||
for(b = picture->description; *b; ) { | |||||
unsigned n = utf8len_(b); | |||||
if(n == 0) { | |||||
if(violation) *violation = "description string must be valid UTF-8"; | |||||
return false; | |||||
} | |||||
b += n; | |||||
} | |||||
return true; | |||||
} | |||||
/* | |||||
* These routines are private to libFLAC | |||||
*/ | |||||
unsigned FLAC__format_get_max_rice_partition_order(unsigned blocksize, unsigned predictor_order) | |||||
{ | |||||
return | |||||
FLAC__format_get_max_rice_partition_order_from_blocksize_limited_max_and_predictor_order( | |||||
FLAC__format_get_max_rice_partition_order_from_blocksize(blocksize), | |||||
blocksize, | |||||
predictor_order | |||||
); | |||||
} | |||||
unsigned FLAC__format_get_max_rice_partition_order_from_blocksize(unsigned blocksize) | |||||
{ | |||||
unsigned max_rice_partition_order = 0; | |||||
while(!(blocksize & 1)) { | |||||
max_rice_partition_order++; | |||||
blocksize >>= 1; | |||||
} | |||||
return min(FLAC__MAX_RICE_PARTITION_ORDER, max_rice_partition_order); | |||||
} | |||||
unsigned FLAC__format_get_max_rice_partition_order_from_blocksize_limited_max_and_predictor_order(unsigned limit, unsigned blocksize, unsigned predictor_order) | |||||
{ | |||||
unsigned max_rice_partition_order = limit; | |||||
while(max_rice_partition_order > 0 && (blocksize >> max_rice_partition_order) <= predictor_order) | |||||
max_rice_partition_order--; | |||||
FLAC__ASSERT( | |||||
(max_rice_partition_order == 0 && blocksize >= predictor_order) || | |||||
(max_rice_partition_order > 0 && blocksize >> max_rice_partition_order > predictor_order) | |||||
); | |||||
return max_rice_partition_order; | |||||
} | |||||
void FLAC__format_entropy_coding_method_partitioned_rice_contents_init(FLAC__EntropyCodingMethod_PartitionedRiceContents *object) | |||||
{ | |||||
FLAC__ASSERT(0 != object); | |||||
object->parameters = 0; | |||||
object->raw_bits = 0; | |||||
object->capacity_by_order = 0; | |||||
} | |||||
void FLAC__format_entropy_coding_method_partitioned_rice_contents_clear(FLAC__EntropyCodingMethod_PartitionedRiceContents *object) | |||||
{ | |||||
FLAC__ASSERT(0 != object); | |||||
if(0 != object->parameters) | |||||
free(object->parameters); | |||||
if(0 != object->raw_bits) | |||||
free(object->raw_bits); | |||||
FLAC__format_entropy_coding_method_partitioned_rice_contents_init(object); | |||||
} | |||||
FLAC__bool FLAC__format_entropy_coding_method_partitioned_rice_contents_ensure_size(FLAC__EntropyCodingMethod_PartitionedRiceContents *object, unsigned max_partition_order) | |||||
{ | |||||
FLAC__ASSERT(0 != object); | |||||
FLAC__ASSERT(object->capacity_by_order > 0 || (0 == object->parameters && 0 == object->raw_bits)); | |||||
if(object->capacity_by_order < max_partition_order) { | |||||
if(0 == (object->parameters = (unsigned*)realloc(object->parameters, sizeof(unsigned)*(1 << max_partition_order)))) | |||||
return false; | |||||
if(0 == (object->raw_bits = (unsigned*)realloc(object->raw_bits, sizeof(unsigned)*(1 << max_partition_order)))) | |||||
return false; | |||||
memset(object->raw_bits, 0, sizeof(unsigned)*(1 << max_partition_order)); | |||||
object->capacity_by_order = max_partition_order; | |||||
} | |||||
return true; | |||||
} |
@@ -0,0 +1,49 @@ | |||||
/* libFLAC - Free Lossless Audio Codec library | |||||
* Copyright (C) 2000,2001,2002,2003,2004,2005,2006,2007 Josh Coalson | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions | |||||
* are met: | |||||
* | |||||
* - Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* | |||||
* - Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* | |||||
* - Neither the name of the Xiph.org Foundation nor the names of its | |||||
* contributors may be used to endorse or promote products derived from | |||||
* this software without specific prior written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR | |||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
*/ | |||||
#ifndef FLAC__PRIVATE__ALL_H | |||||
#define FLAC__PRIVATE__ALL_H | |||||
#include "bitmath.h" | |||||
#include "bitreader.h" | |||||
#include "bitwriter.h" | |||||
#include "cpu.h" | |||||
#include "crc.h" | |||||
#include "fixed.h" | |||||
#include "float.h" | |||||
#include "format.h" | |||||
#include "lpc.h" | |||||
#include "md5.h" | |||||
#include "memory.h" | |||||
#include "metadata.h" | |||||
#include "stream_encoder_framing.h" | |||||
#endif |
@@ -0,0 +1,42 @@ | |||||
/* libFLAC - Free Lossless Audio Codec library | |||||
* Copyright (C) 2001,2002,2003,2004,2005,2006,2007 Josh Coalson | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions | |||||
* are met: | |||||
* | |||||
* - Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* | |||||
* - Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* | |||||
* - Neither the name of the Xiph.org Foundation nor the names of its | |||||
* contributors may be used to endorse or promote products derived from | |||||
* this software without specific prior written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR | |||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
*/ | |||||
#ifndef FLAC__PRIVATE__BITMATH_H | |||||
#define FLAC__PRIVATE__BITMATH_H | |||||
#include "../../../ordinals.h" | |||||
unsigned FLAC__bitmath_ilog2(FLAC__uint32 v); | |||||
unsigned FLAC__bitmath_ilog2_wide(FLAC__uint64 v); | |||||
unsigned FLAC__bitmath_silog2(int v); | |||||
unsigned FLAC__bitmath_silog2_wide(FLAC__int64 v); | |||||
#endif |