Browse Source

Import juce_audio_basics code

tags/1.9.4
falkTX 11 years ago
parent
commit
c271861bbd
46 changed files with 11235 additions and 0 deletions
  1. +14
    -0
      source/modules/juce_audio_basics/AppConfig.h
  2. +88
    -0
      source/modules/juce_audio_basics/Makefile
  3. +600
    -0
      source/modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp
  4. +691
    -0
      source/modules/juce_audio_basics/buffers/juce_AudioDataConverters.h
  5. +542
    -0
      source/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.cpp
  6. +444
    -0
      source/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h
  7. +344
    -0
      source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp
  8. +79
    -0
      source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h
  9. +103
    -0
      source/modules/juce_audio_basics/effects/juce_Decibels.h
  10. +245
    -0
      source/modules/juce_audio_basics/effects/juce_IIRFilter.cpp
  11. +172
    -0
      source/modules/juce_audio_basics/effects/juce_IIRFilter.h
  12. +200
    -0
      source/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.cpp
  13. +93
    -0
      source/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.h
  14. +323
    -0
      source/modules/juce_audio_basics/effects/juce_Reverb.h
  15. +80
    -0
      source/modules/juce_audio_basics/juce_audio_basics.cpp
  16. +61
    -0
      source/modules/juce_audio_basics/juce_audio_basics.h
  17. +25
    -0
      source/modules/juce_audio_basics/juce_audio_basics.mm
  18. +24
    -0
      source/modules/juce_audio_basics/juce_module_info
  19. +289
    -0
      source/modules/juce_audio_basics/midi/juce_MidiBuffer.cpp
  20. +240
    -0
      source/modules/juce_audio_basics/midi/juce_MidiBuffer.h
  21. +424
    -0
      source/modules/juce_audio_basics/midi/juce_MidiFile.cpp
  22. +186
    -0
      source/modules/juce_audio_basics/midi/juce_MidiFile.h
  23. +183
    -0
      source/modules/juce_audio_basics/midi/juce_MidiKeyboardState.cpp
  24. +206
    -0
      source/modules/juce_audio_basics/midi/juce_MidiKeyboardState.h
  25. +1048
    -0
      source/modules/juce_audio_basics/midi/juce_MidiMessage.cpp
  26. +939
    -0
      source/modules/juce_audio_basics/midi/juce_MidiMessage.h
  27. +334
    -0
      source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp
  28. +280
    -0
      source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h
  29. +183
    -0
      source/modules/juce_audio_basics/sources/juce_AudioSource.h
  30. +260
    -0
      source/modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp
  31. +113
    -0
      source/modules/juce_audio_basics/sources/juce_BufferingAudioSource.h
  32. +185
    -0
      source/modules/juce_audio_basics/sources/juce_ChannelRemappingAudioSource.cpp
  33. +148
    -0
      source/modules/juce_audio_basics/sources/juce_ChannelRemappingAudioSource.h
  34. +77
    -0
      source/modules/juce_audio_basics/sources/juce_IIRFilterAudioSource.cpp
  35. +73
    -0
      source/modules/juce_audio_basics/sources/juce_IIRFilterAudioSource.h
  36. +157
    -0
      source/modules/juce_audio_basics/sources/juce_MixerAudioSource.cpp
  37. +103
    -0
      source/modules/juce_audio_basics/sources/juce_MixerAudioSource.h
  38. +80
    -0
      source/modules/juce_audio_basics/sources/juce_PositionableAudioSource.h
  39. +257
    -0
      source/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.cpp
  40. +105
    -0
      source/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.h
  41. +80
    -0
      source/modules/juce_audio_basics/sources/juce_ReverbAudioSource.cpp
  42. +79
    -0
      source/modules/juce_audio_basics/sources/juce_ReverbAudioSource.h
  43. +76
    -0
      source/modules/juce_audio_basics/sources/juce_ToneGeneratorAudioSource.cpp
  44. +75
    -0
      source/modules/juce_audio_basics/sources/juce_ToneGeneratorAudioSource.h
  45. +432
    -0
      source/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp
  46. +495
    -0
      source/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h

+ 14
- 0
source/modules/juce_audio_basics/AppConfig.h View File

@@ -0,0 +1,14 @@
/*
==============================================================================
Build options for juce_audio_basics static library
==============================================================================
*/
#ifndef CARLA_JUCE_AUDIO_BASICS_APPCONFIG_H_INCLUDED
#define CARLA_JUCE_AUDIO_BASICS_APPCONFIG_H_INCLUDED
#include "../juce_core/AppConfig.h"
#endif // CARLA_JUCE_AUDIO_BASICS_APPCONFIG_H_INCLUDED

+ 88
- 0
source/modules/juce_audio_basics/Makefile View File

@@ -0,0 +1,88 @@
#!/usr/bin/make -f
# Makefile for juce_audio_basics #
# ------------------------------ #
# Created by falkTX
#

include ../../Makefile.mk

# --------------------------------------------------------------

BUILD_CXX_FLAGS += -I.

ifeq ($(MACOS),true)
BUILD_CXX_FLAGS += -objc++
OBJS = juce_audio_basics.mm.o
OBJS_posix32 = juce_audio_basics.mm.posix32.o
OBJS_posix64 = juce_audio_basics.mm.posix64.o
else
OBJS = juce_audio_basics.cpp.o
OBJS_posix32 = juce_audio_basics.cpp.posix32.o
OBJS_posix64 = juce_audio_basics.cpp.posix64.o
endif

OBJS_win32 = juce_audio_basics.cpp.win32.o
OBJS_win64 = juce_audio_basics.cpp.win64.o

# --------------------------------------------------------------

all: ../juce_audio_basics.a

posix32: ../juce_audio_basics.posix32.a
posix64: ../juce_audio_basics.posix64.a
win32: ../juce_audio_basics.win32.a
win64: ../juce_audio_basics.win64.a

# --------------------------------------------------------------

../juce_audio_basics.a: $(OBJS)
$(AR) rs $@ $^

../juce_audio_basics.posix32.a: $(OBJS_posix32)
$(AR) rs $@ $^

../juce_audio_basics.posix64.a: $(OBJS_posix64)
$(AR) rs $@ $^

../juce_audio_basics.win32.a: $(OBJS_win32)
$(AR) rs $@ $^

../juce_audio_basics.win64.a: $(OBJS_win64)
$(AR) rs $@ $^

../juce_audio_basics.dll: $(OBJS)
$(CXX) $^ -shared $(LINK_FLAGS) -o $@

../juce_audio_basics.dylib: $(OBJS)
$(CXX) $^ -dynamiclib $(LINK_FLAGS) -o $@

../juce_audio_basics.so: $(OBJS)
$(CXX) $^ -shared $(LINK_FLAGS) -o $@

# --------------------------------------------------------------

%.cpp.o: %.cpp
$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@

%.mm.o: %.mm
$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@

%.posix32.o: %
$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -c -o $@

%.posix64.o: %
$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -c -o $@

%.win32.o: %
$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -c -o $@

%.win64.o: %
$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -c -o $@

# --------------------------------------------------------------

clean:
rm -f *.o ../juce_audio_basics.*

debug:
$(MAKE) DEBUG=true

+ 600
- 0
source/modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp View File

@@ -0,0 +1,600 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
void AudioDataConverters::convertFloatToInt16LE (const float* source, void* dest, int numSamples, const int destBytesPerSample)
{
const double maxVal = (double) 0x7fff;
char* intData = static_cast <char*> (dest);
if (dest != (void*) source || destBytesPerSample <= 4)
{
for (int i = 0; i < numSamples; ++i)
{
*(uint16*) intData = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
intData += destBytesPerSample;
}
}
else
{
intData += destBytesPerSample * numSamples;
for (int i = numSamples; --i >= 0;)
{
intData -= destBytesPerSample;
*(uint16*) intData = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
}
}
}
void AudioDataConverters::convertFloatToInt16BE (const float* source, void* dest, int numSamples, const int destBytesPerSample)
{
const double maxVal = (double) 0x7fff;
char* intData = static_cast <char*> (dest);
if (dest != (void*) source || destBytesPerSample <= 4)
{
for (int i = 0; i < numSamples; ++i)
{
*(uint16*) intData = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
intData += destBytesPerSample;
}
}
else
{
intData += destBytesPerSample * numSamples;
for (int i = numSamples; --i >= 0;)
{
intData -= destBytesPerSample;
*(uint16*) intData = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
}
}
}
void AudioDataConverters::convertFloatToInt24LE (const float* source, void* dest, int numSamples, const int destBytesPerSample)
{
const double maxVal = (double) 0x7fffff;
char* intData = static_cast <char*> (dest);
if (dest != (void*) source || destBytesPerSample <= 4)
{
for (int i = 0; i < numSamples; ++i)
{
ByteOrder::littleEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData);
intData += destBytesPerSample;
}
}
else
{
intData += destBytesPerSample * numSamples;
for (int i = numSamples; --i >= 0;)
{
intData -= destBytesPerSample;
ByteOrder::littleEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData);
}
}
}
void AudioDataConverters::convertFloatToInt24BE (const float* source, void* dest, int numSamples, const int destBytesPerSample)
{
const double maxVal = (double) 0x7fffff;
char* intData = static_cast <char*> (dest);
if (dest != (void*) source || destBytesPerSample <= 4)
{
for (int i = 0; i < numSamples; ++i)
{
ByteOrder::bigEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData);
intData += destBytesPerSample;
}
}
else
{
intData += destBytesPerSample * numSamples;
for (int i = numSamples; --i >= 0;)
{
intData -= destBytesPerSample;
ByteOrder::bigEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData);
}
}
}
void AudioDataConverters::convertFloatToInt32LE (const float* source, void* dest, int numSamples, const int destBytesPerSample)
{
const double maxVal = (double) 0x7fffffff;
char* intData = static_cast <char*> (dest);
if (dest != (void*) source || destBytesPerSample <= 4)
{
for (int i = 0; i < numSamples; ++i)
{
*(uint32*)intData = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
intData += destBytesPerSample;
}
}
else
{
intData += destBytesPerSample * numSamples;
for (int i = numSamples; --i >= 0;)
{
intData -= destBytesPerSample;
*(uint32*)intData = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
}
}
}
void AudioDataConverters::convertFloatToInt32BE (const float* source, void* dest, int numSamples, const int destBytesPerSample)
{
const double maxVal = (double) 0x7fffffff;
char* intData = static_cast <char*> (dest);
if (dest != (void*) source || destBytesPerSample <= 4)
{
for (int i = 0; i < numSamples; ++i)
{
*(uint32*)intData = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
intData += destBytesPerSample;
}
}
else
{
intData += destBytesPerSample * numSamples;
for (int i = numSamples; --i >= 0;)
{
intData -= destBytesPerSample;
*(uint32*)intData = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])));
}
}
}
void AudioDataConverters::convertFloatToFloat32LE (const float* source, void* dest, int numSamples, const int destBytesPerSample)
{
jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data!
char* d = static_cast <char*> (dest);
for (int i = 0; i < numSamples; ++i)
{
*(float*) d = source[i];
#if JUCE_BIG_ENDIAN
*(uint32*) d = ByteOrder::swap (*(uint32*) d);
#endif
d += destBytesPerSample;
}
}
void AudioDataConverters::convertFloatToFloat32BE (const float* source, void* dest, int numSamples, const int destBytesPerSample)
{
jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data!
char* d = static_cast <char*> (dest);
for (int i = 0; i < numSamples; ++i)
{
*(float*) d = source[i];
#if JUCE_LITTLE_ENDIAN
*(uint32*) d = ByteOrder::swap (*(uint32*) d);
#endif
d += destBytesPerSample;
}
}
//==============================================================================
void AudioDataConverters::convertInt16LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample)
{
const float scale = 1.0f / 0x7fff;
const char* intData = static_cast <const char*> (source);
if (source != (void*) dest || srcBytesPerSample >= 4)
{
for (int i = 0; i < numSamples; ++i)
{
dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*(uint16*)intData);
intData += srcBytesPerSample;
}
}
else
{
intData += srcBytesPerSample * numSamples;
for (int i = numSamples; --i >= 0;)
{
intData -= srcBytesPerSample;
dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*(uint16*)intData);
}
}
}
void AudioDataConverters::convertInt16BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample)
{
const float scale = 1.0f / 0x7fff;
const char* intData = static_cast <const char*> (source);
if (source != (void*) dest || srcBytesPerSample >= 4)
{
for (int i = 0; i < numSamples; ++i)
{
dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*(uint16*)intData);
intData += srcBytesPerSample;
}
}
else
{
intData += srcBytesPerSample * numSamples;
for (int i = numSamples; --i >= 0;)
{
intData -= srcBytesPerSample;
dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*(uint16*)intData);
}
}
}
void AudioDataConverters::convertInt24LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample)
{
const float scale = 1.0f / 0x7fffff;
const char* intData = static_cast <const char*> (source);
if (source != (void*) dest || srcBytesPerSample >= 4)
{
for (int i = 0; i < numSamples; ++i)
{
dest[i] = scale * (short) ByteOrder::littleEndian24Bit (intData);
intData += srcBytesPerSample;
}
}
else
{
intData += srcBytesPerSample * numSamples;
for (int i = numSamples; --i >= 0;)
{
intData -= srcBytesPerSample;
dest[i] = scale * (short) ByteOrder::littleEndian24Bit (intData);
}
}
}
void AudioDataConverters::convertInt24BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample)
{
const float scale = 1.0f / 0x7fffff;
const char* intData = static_cast <const char*> (source);
if (source != (void*) dest || srcBytesPerSample >= 4)
{
for (int i = 0; i < numSamples; ++i)
{
dest[i] = scale * (short) ByteOrder::bigEndian24Bit (intData);
intData += srcBytesPerSample;
}
}
else
{
intData += srcBytesPerSample * numSamples;
for (int i = numSamples; --i >= 0;)
{
intData -= srcBytesPerSample;
dest[i] = scale * (short) ByteOrder::bigEndian24Bit (intData);
}
}
}
void AudioDataConverters::convertInt32LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample)
{
const float scale = 1.0f / 0x7fffffff;
const char* intData = static_cast <const char*> (source);
if (source != (void*) dest || srcBytesPerSample >= 4)
{
for (int i = 0; i < numSamples; ++i)
{
dest[i] = scale * (int) ByteOrder::swapIfBigEndian (*(uint32*) intData);
intData += srcBytesPerSample;
}
}
else
{
intData += srcBytesPerSample * numSamples;
for (int i = numSamples; --i >= 0;)
{
intData -= srcBytesPerSample;
dest[i] = scale * (int) ByteOrder::swapIfBigEndian (*(uint32*) intData);
}
}
}
void AudioDataConverters::convertInt32BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample)
{
const float scale = 1.0f / 0x7fffffff;
const char* intData = static_cast <const char*> (source);
if (source != (void*) dest || srcBytesPerSample >= 4)
{
for (int i = 0; i < numSamples; ++i)
{
dest[i] = scale * (int) ByteOrder::swapIfLittleEndian (*(uint32*) intData);
intData += srcBytesPerSample;
}
}
else
{
intData += srcBytesPerSample * numSamples;
for (int i = numSamples; --i >= 0;)
{
intData -= srcBytesPerSample;
dest[i] = scale * (int) ByteOrder::swapIfLittleEndian (*(uint32*) intData);
}
}
}
void AudioDataConverters::convertFloat32LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample)
{
const char* s = static_cast <const char*> (source);
for (int i = 0; i < numSamples; ++i)
{
dest[i] = *(float*)s;
#if JUCE_BIG_ENDIAN
uint32* const d = (uint32*) (dest + i);
*d = ByteOrder::swap (*d);
#endif
s += srcBytesPerSample;
}
}
void AudioDataConverters::convertFloat32BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample)
{
const char* s = static_cast <const char*> (source);
for (int i = 0; i < numSamples; ++i)
{
dest[i] = *(float*)s;
#if JUCE_LITTLE_ENDIAN
uint32* const d = (uint32*) (dest + i);
*d = ByteOrder::swap (*d);
#endif
s += srcBytesPerSample;
}
}
//==============================================================================
void AudioDataConverters::convertFloatToFormat (const DataFormat destFormat,
const float* const source,
void* const dest,
const int numSamples)
{
switch (destFormat)
{
case int16LE: convertFloatToInt16LE (source, dest, numSamples); break;
case int16BE: convertFloatToInt16BE (source, dest, numSamples); break;
case int24LE: convertFloatToInt24LE (source, dest, numSamples); break;
case int24BE: convertFloatToInt24BE (source, dest, numSamples); break;
case int32LE: convertFloatToInt32LE (source, dest, numSamples); break;
case int32BE: convertFloatToInt32BE (source, dest, numSamples); break;
case float32LE: convertFloatToFloat32LE (source, dest, numSamples); break;
case float32BE: convertFloatToFloat32BE (source, dest, numSamples); break;
default: jassertfalse; break;
}
}
void AudioDataConverters::convertFormatToFloat (const DataFormat sourceFormat,
const void* const source,
float* const dest,
const int numSamples)
{
switch (sourceFormat)
{
case int16LE: convertInt16LEToFloat (source, dest, numSamples); break;
case int16BE: convertInt16BEToFloat (source, dest, numSamples); break;
case int24LE: convertInt24LEToFloat (source, dest, numSamples); break;
case int24BE: convertInt24BEToFloat (source, dest, numSamples); break;
case int32LE: convertInt32LEToFloat (source, dest, numSamples); break;
case int32BE: convertInt32BEToFloat (source, dest, numSamples); break;
case float32LE: convertFloat32LEToFloat (source, dest, numSamples); break;
case float32BE: convertFloat32BEToFloat (source, dest, numSamples); break;
default: jassertfalse; break;
}
}
//==============================================================================
void AudioDataConverters::interleaveSamples (const float** const source,
float* const dest,
const int numSamples,
const int numChannels)
{
for (int chan = 0; chan < numChannels; ++chan)
{
int i = chan;
const float* src = source [chan];
for (int j = 0; j < numSamples; ++j)
{
dest [i] = src [j];
i += numChannels;
}
}
}
void AudioDataConverters::deinterleaveSamples (const float* const source,
float** const dest,
const int numSamples,
const int numChannels)
{
for (int chan = 0; chan < numChannels; ++chan)
{
int i = chan;
float* dst = dest [chan];
for (int j = 0; j < numSamples; ++j)
{
dst [j] = source [i];
i += numChannels;
}
}
}
//==============================================================================
#if JUCE_UNIT_TESTS
class AudioConversionTests : public UnitTest
{
public:
AudioConversionTests() : UnitTest ("Audio data conversion") {}
template <class F1, class E1, class F2, class E2>
struct Test5
{
static void test (UnitTest& unitTest)
{
test (unitTest, false);
test (unitTest, true);
}
static void test (UnitTest& unitTest, bool inPlace)
{
const int numSamples = 2048;
int32 original [numSamples], converted [numSamples], reversed [numSamples];
Random r;
{
AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::NonConst> d (original);
bool clippingFailed = false;
for (int i = 0; i < numSamples / 2; ++i)
{
d.setAsFloat (r.nextFloat() * 2.2f - 1.1f);
if (! d.isFloatingPoint())
clippingFailed = d.getAsFloat() > 1.0f || d.getAsFloat() < -1.0f || clippingFailed;
++d;
d.setAsInt32 (r.nextInt());
++d;
}
unitTest.expect (! clippingFailed);
}
// convert data from the source to dest format..
ScopedPointer<AudioData::Converter> conv (new AudioData::ConverterInstance <AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const>,
AudioData::Pointer<F2, E2, AudioData::NonInterleaved, AudioData::NonConst> >());
conv->convertSamples (inPlace ? reversed : converted, original, numSamples);
// ..and back again..
conv = new AudioData::ConverterInstance <AudioData::Pointer<F2, E2, AudioData::NonInterleaved, AudioData::Const>,
AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::NonConst> >();
if (! inPlace)
zeromem (reversed, sizeof (reversed));
conv->convertSamples (reversed, inPlace ? reversed : converted, numSamples);
{
int biggestDiff = 0;
AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const> d1 (original);
AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const> d2 (reversed);
const int errorMargin = 2 * AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const>::get32BitResolution()
+ AudioData::Pointer<F2, E2, AudioData::NonInterleaved, AudioData::Const>::get32BitResolution();
for (int i = 0; i < numSamples; ++i)
{
biggestDiff = jmax (biggestDiff, std::abs (d1.getAsInt32() - d2.getAsInt32()));
++d1;
++d2;
}
unitTest.expect (biggestDiff <= errorMargin);
}
}
};
template <class F1, class E1, class FormatType>
struct Test3
{
static void test (UnitTest& unitTest)
{
Test5 <F1, E1, FormatType, AudioData::BigEndian>::test (unitTest);
Test5 <F1, E1, FormatType, AudioData::LittleEndian>::test (unitTest);
}
};
template <class FormatType, class Endianness>
struct Test2
{
static void test (UnitTest& unitTest)
{
Test3 <FormatType, Endianness, AudioData::Int8>::test (unitTest);
Test3 <FormatType, Endianness, AudioData::UInt8>::test (unitTest);
Test3 <FormatType, Endianness, AudioData::Int16>::test (unitTest);
Test3 <FormatType, Endianness, AudioData::Int24>::test (unitTest);
Test3 <FormatType, Endianness, AudioData::Int32>::test (unitTest);
Test3 <FormatType, Endianness, AudioData::Float32>::test (unitTest);
}
};
template <class FormatType>
struct Test1
{
static void test (UnitTest& unitTest)
{
Test2 <FormatType, AudioData::BigEndian>::test (unitTest);
Test2 <FormatType, AudioData::LittleEndian>::test (unitTest);
}
};
void runTest()
{
beginTest ("Round-trip conversion: Int8");
Test1 <AudioData::Int8>::test (*this);
beginTest ("Round-trip conversion: Int16");
Test1 <AudioData::Int16>::test (*this);
beginTest ("Round-trip conversion: Int24");
Test1 <AudioData::Int24>::test (*this);
beginTest ("Round-trip conversion: Int32");
Test1 <AudioData::Int32>::test (*this);
beginTest ("Round-trip conversion: Float32");
Test1 <AudioData::Float32>::test (*this);
}
};
static AudioConversionTests audioConversionUnitTests;
#endif

+ 691
- 0
source/modules/juce_audio_basics/buffers/juce_AudioDataConverters.h View File

@@ -0,0 +1,691 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_AUDIODATACONVERTERS_H_INCLUDED
#define JUCE_AUDIODATACONVERTERS_H_INCLUDED
//==============================================================================
/**
This class a container which holds all the classes pertaining to the AudioData::Pointer
audio sample format class.
@see AudioData::Pointer.
*/
class JUCE_API AudioData
{
public:
//==============================================================================
// These types can be used as the SampleFormat template parameter for the AudioData::Pointer class.
class Int8; /**< Used as a template parameter for AudioData::Pointer. Indicates an 8-bit integer packed data format. */
class UInt8; /**< Used as a template parameter for AudioData::Pointer. Indicates an 8-bit unsigned integer packed data format. */
class Int16; /**< Used as a template parameter for AudioData::Pointer. Indicates an 16-bit integer packed data format. */
class Int24; /**< Used as a template parameter for AudioData::Pointer. Indicates an 24-bit integer packed data format. */
class Int32; /**< Used as a template parameter for AudioData::Pointer. Indicates an 32-bit integer packed data format. */
class Float32; /**< Used as a template parameter for AudioData::Pointer. Indicates an 32-bit float data format. */
//==============================================================================
// These types can be used as the Endianness template parameter for the AudioData::Pointer class.
class BigEndian; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored in big-endian order. */
class LittleEndian; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored in little-endian order. */
class NativeEndian; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored in the CPU's native endianness. */
//==============================================================================
// These types can be used as the InterleavingType template parameter for the AudioData::Pointer class.
class NonInterleaved; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored contiguously. */
class Interleaved; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are interleaved with a number of other channels. */
//==============================================================================
// These types can be used as the Constness template parameter for the AudioData::Pointer class.
class NonConst; /**< Used as a template parameter for AudioData::Pointer. Indicates that the pointer can be used for non-const data. */
class Const; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples can only be used for const data.. */
#ifndef DOXYGEN
//==============================================================================
class BigEndian
{
public:
template <class SampleFormatType> static inline float getAsFloat (SampleFormatType& s) noexcept { return s.getAsFloatBE(); }
template <class SampleFormatType> static inline void setAsFloat (SampleFormatType& s, float newValue) noexcept { s.setAsFloatBE (newValue); }
template <class SampleFormatType> static inline int32 getAsInt32 (SampleFormatType& s) noexcept { return s.getAsInt32BE(); }
template <class SampleFormatType> static inline void setAsInt32 (SampleFormatType& s, int32 newValue) noexcept { s.setAsInt32BE (newValue); }
template <class SourceType, class DestType> static inline void copyFrom (DestType& dest, SourceType& source) noexcept { dest.copyFromBE (source); }
enum { isBigEndian = 1 };
};
class LittleEndian
{
public:
template <class SampleFormatType> static inline float getAsFloat (SampleFormatType& s) noexcept { return s.getAsFloatLE(); }
template <class SampleFormatType> static inline void setAsFloat (SampleFormatType& s, float newValue) noexcept { s.setAsFloatLE (newValue); }
template <class SampleFormatType> static inline int32 getAsInt32 (SampleFormatType& s) noexcept { return s.getAsInt32LE(); }
template <class SampleFormatType> static inline void setAsInt32 (SampleFormatType& s, int32 newValue) noexcept { s.setAsInt32LE (newValue); }
template <class SourceType, class DestType> static inline void copyFrom (DestType& dest, SourceType& source) noexcept { dest.copyFromLE (source); }
enum { isBigEndian = 0 };
};
#if JUCE_BIG_ENDIAN
class NativeEndian : public BigEndian {};
#else
class NativeEndian : public LittleEndian {};
#endif
//==============================================================================
class Int8
{
public:
inline Int8 (void* d) noexcept : data (static_cast <int8*> (d)) {}
inline void advance() noexcept { ++data; }
inline void skip (int numSamples) noexcept { data += numSamples; }
inline float getAsFloatLE() const noexcept { return (float) (*data * (1.0 / (1.0 + maxValue))); }
inline float getAsFloatBE() const noexcept { return getAsFloatLE(); }
inline void setAsFloatLE (float newValue) noexcept { *data = (int8) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + maxValue))); }
inline void setAsFloatBE (float newValue) noexcept { setAsFloatLE (newValue); }
inline int32 getAsInt32LE() const noexcept { return (int) (*data << 24); }
inline int32 getAsInt32BE() const noexcept { return getAsInt32LE(); }
inline void setAsInt32LE (int newValue) noexcept { *data = (int8) (newValue >> 24); }
inline void setAsInt32BE (int newValue) noexcept { setAsInt32LE (newValue); }
inline void clear() noexcept { *data = 0; }
inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;}
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); }
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); }
inline void copyFromSameType (Int8& source) noexcept { *data = *source.data; }
int8* data;
enum { bytesPerSample = 1, maxValue = 0x7f, resolution = (1 << 24), isFloat = 0 };
};
class UInt8
{
public:
inline UInt8 (void* d) noexcept : data (static_cast <uint8*> (d)) {}
inline void advance() noexcept { ++data; }
inline void skip (int numSamples) noexcept { data += numSamples; }
inline float getAsFloatLE() const noexcept { return (float) ((*data - 128) * (1.0 / (1.0 + maxValue))); }
inline float getAsFloatBE() const noexcept { return getAsFloatLE(); }
inline void setAsFloatLE (float newValue) noexcept { *data = (uint8) jlimit (0, 255, 128 + roundToInt (newValue * (1.0 + maxValue))); }
inline void setAsFloatBE (float newValue) noexcept { setAsFloatLE (newValue); }
inline int32 getAsInt32LE() const noexcept { return (int) ((*data - 128) << 24); }
inline int32 getAsInt32BE() const noexcept { return getAsInt32LE(); }
inline void setAsInt32LE (int newValue) noexcept { *data = (uint8) (128 + (newValue >> 24)); }
inline void setAsInt32BE (int newValue) noexcept { setAsInt32LE (newValue); }
inline void clear() noexcept { *data = 128; }
inline void clearMultiple (int num) noexcept { memset (data, 128, (size_t) num) ;}
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); }
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); }
inline void copyFromSameType (UInt8& source) noexcept { *data = *source.data; }
uint8* data;
enum { bytesPerSample = 1, maxValue = 0x7f, resolution = (1 << 24), isFloat = 0 };
};
class Int16
{
public:
inline Int16 (void* d) noexcept : data (static_cast <uint16*> (d)) {}
inline void advance() noexcept { ++data; }
inline void skip (int numSamples) noexcept { data += numSamples; }
inline float getAsFloatLE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int16) ByteOrder::swapIfBigEndian (*data)); }
inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int16) ByteOrder::swapIfLittleEndian (*data)); }
inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint16) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + maxValue)))); }
inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint16) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + maxValue)))); }
inline int32 getAsInt32LE() const noexcept { return (int32) (ByteOrder::swapIfBigEndian ((uint16) *data) << 16); }
inline int32 getAsInt32BE() const noexcept { return (int32) (ByteOrder::swapIfLittleEndian ((uint16) *data) << 16); }
inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint16) (newValue >> 16)); }
inline void setAsInt32BE (int32 newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint16) (newValue >> 16)); }
inline void clear() noexcept { *data = 0; }
inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;}
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); }
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); }
inline void copyFromSameType (Int16& source) noexcept { *data = *source.data; }
uint16* data;
enum { bytesPerSample = 2, maxValue = 0x7fff, resolution = (1 << 16), isFloat = 0 };
};
class Int24
{
public:
inline Int24 (void* d) noexcept : data (static_cast <char*> (d)) {}
inline void advance() noexcept { data += 3; }
inline void skip (int numSamples) noexcept { data += 3 * numSamples; }
inline float getAsFloatLE() const noexcept { return (float) (ByteOrder::littleEndian24Bit (data) * (1.0 / (1.0 + maxValue))); }
inline float getAsFloatBE() const noexcept { return (float) (ByteOrder::bigEndian24Bit (data) * (1.0 / (1.0 + maxValue))); }
inline void setAsFloatLE (float newValue) noexcept { ByteOrder::littleEndian24BitToChars (jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + maxValue))), data); }
inline void setAsFloatBE (float newValue) noexcept { ByteOrder::bigEndian24BitToChars (jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + maxValue))), data); }
inline int32 getAsInt32LE() const noexcept { return (int32) ByteOrder::littleEndian24Bit (data) << 8; }
inline int32 getAsInt32BE() const noexcept { return (int32) ByteOrder::bigEndian24Bit (data) << 8; }
inline void setAsInt32LE (int32 newValue) noexcept { ByteOrder::littleEndian24BitToChars (newValue >> 8, data); }
inline void setAsInt32BE (int32 newValue) noexcept { ByteOrder::bigEndian24BitToChars (newValue >> 8, data); }
inline void clear() noexcept { data[0] = 0; data[1] = 0; data[2] = 0; }
inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;}
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); }
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); }
inline void copyFromSameType (Int24& source) noexcept { data[0] = source.data[0]; data[1] = source.data[1]; data[2] = source.data[2]; }
char* data;
enum { bytesPerSample = 3, maxValue = 0x7fffff, resolution = (1 << 8), isFloat = 0 };
};
class Int32
{
public:
inline Int32 (void* d) noexcept : data (static_cast <uint32*> (d)) {}
inline void advance() noexcept { ++data; }
inline void skip (int numSamples) noexcept { data += numSamples; }
inline float getAsFloatLE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int32) ByteOrder::swapIfBigEndian (*data)); }
inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int32) ByteOrder::swapIfLittleEndian (*data)); }
inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) (maxValue * jlimit (-1.0, 1.0, (double) newValue))); }
inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) (maxValue * jlimit (-1.0, 1.0, (double) newValue))); }
inline int32 getAsInt32LE() const noexcept { return (int32) ByteOrder::swapIfBigEndian (*data); }
inline int32 getAsInt32BE() const noexcept { return (int32) ByteOrder::swapIfLittleEndian (*data); }
inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) newValue); }
inline void setAsInt32BE (int32 newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) newValue); }
inline void clear() noexcept { *data = 0; }
inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;}
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); }
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); }
inline void copyFromSameType (Int32& source) noexcept { *data = *source.data; }
uint32* data;
enum { bytesPerSample = 4, maxValue = 0x7fffffff, resolution = 1, isFloat = 0 };
};
class Float32
{
public:
inline Float32 (void* d) noexcept : data (static_cast <float*> (d)) {}
inline void advance() noexcept { ++data; }
inline void skip (int numSamples) noexcept { data += numSamples; }
#if JUCE_BIG_ENDIAN
inline float getAsFloatBE() const noexcept { return *data; }
inline void setAsFloatBE (float newValue) noexcept { *data = newValue; }
inline float getAsFloatLE() const noexcept { union { uint32 asInt; float asFloat; } n; n.asInt = ByteOrder::swap (*(uint32*) data); return n.asFloat; }
inline void setAsFloatLE (float newValue) noexcept { union { uint32 asInt; float asFloat; } n; n.asFloat = newValue; *(uint32*) data = ByteOrder::swap (n.asInt); }
#else
inline float getAsFloatLE() const noexcept { return *data; }
inline void setAsFloatLE (float newValue) noexcept { *data = newValue; }
inline float getAsFloatBE() const noexcept { union { uint32 asInt; float asFloat; } n; n.asInt = ByteOrder::swap (*(uint32*) data); return n.asFloat; }
inline void setAsFloatBE (float newValue) noexcept { union { uint32 asInt; float asFloat; } n; n.asFloat = newValue; *(uint32*) data = ByteOrder::swap (n.asInt); }
#endif
inline int32 getAsInt32LE() const noexcept { return (int32) roundToInt (jlimit (-1.0, 1.0, (double) getAsFloatLE()) * (double) maxValue); }
inline int32 getAsInt32BE() const noexcept { return (int32) roundToInt (jlimit (-1.0, 1.0, (double) getAsFloatBE()) * (double) maxValue); }
inline void setAsInt32LE (int32 newValue) noexcept { setAsFloatLE ((float) (newValue * (1.0 / (1.0 + maxValue)))); }
inline void setAsInt32BE (int32 newValue) noexcept { setAsFloatBE ((float) (newValue * (1.0 / (1.0 + maxValue)))); }
inline void clear() noexcept { *data = 0; }
inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;}
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsFloatLE (source.getAsFloat()); }
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsFloatBE (source.getAsFloat()); }
inline void copyFromSameType (Float32& source) noexcept { *data = *source.data; }
float* data;
enum { bytesPerSample = 4, maxValue = 0x7fffffff, resolution = (1 << 8), isFloat = 1 };
};
//==============================================================================
class NonInterleaved
{
public:
inline NonInterleaved() noexcept {}
inline NonInterleaved (const NonInterleaved&) noexcept {}
inline NonInterleaved (const int) noexcept {}
inline void copyFrom (const NonInterleaved&) noexcept {}
template <class SampleFormatType> inline void advanceData (SampleFormatType& s) noexcept { s.advance(); }
template <class SampleFormatType> inline void advanceDataBy (SampleFormatType& s, int numSamples) noexcept { s.skip (numSamples); }
template <class SampleFormatType> inline void clear (SampleFormatType& s, int numSamples) noexcept { s.clearMultiple (numSamples); }
template <class SampleFormatType> inline static int getNumBytesBetweenSamples (const SampleFormatType&) noexcept { return SampleFormatType::bytesPerSample; }
enum { isInterleavedType = 0, numInterleavedChannels = 1 };
};
class Interleaved
{
public:
inline Interleaved() noexcept : numInterleavedChannels (1) {}
inline Interleaved (const Interleaved& other) noexcept : numInterleavedChannels (other.numInterleavedChannels) {}
inline Interleaved (const int numInterleavedChans) noexcept : numInterleavedChannels (numInterleavedChans) {}
inline void copyFrom (const Interleaved& other) noexcept { numInterleavedChannels = other.numInterleavedChannels; }
template <class SampleFormatType> inline void advanceData (SampleFormatType& s) noexcept { s.skip (numInterleavedChannels); }
template <class SampleFormatType> inline void advanceDataBy (SampleFormatType& s, int numSamples) noexcept { s.skip (numInterleavedChannels * numSamples); }
template <class SampleFormatType> inline void clear (SampleFormatType& s, int numSamples) noexcept { while (--numSamples >= 0) { s.clear(); s.skip (numInterleavedChannels); } }
template <class SampleFormatType> inline int getNumBytesBetweenSamples (const SampleFormatType&) const noexcept { return numInterleavedChannels * SampleFormatType::bytesPerSample; }
int numInterleavedChannels;
enum { isInterleavedType = 1 };
};
//==============================================================================
class NonConst
{
public:
typedef void VoidType;
static inline void* toVoidPtr (VoidType* v) noexcept { return v; }
enum { isConst = 0 };
};
class Const
{
public:
typedef const void VoidType;
static inline void* toVoidPtr (VoidType* v) noexcept { return const_cast <void*> (v); }
enum { isConst = 1 };
};
#endif
//==============================================================================
/**
A pointer to a block of audio data with a particular encoding.
This object can be used to read and write from blocks of encoded audio samples. To create one, you specify
the audio format as a series of template parameters, e.g.
@code
// this creates a pointer for reading from a const array of 16-bit little-endian packed samples.
AudioData::Pointer <AudioData::Int16,
AudioData::LittleEndian,
AudioData::NonInterleaved,
AudioData::Const> pointer (someRawAudioData);
// These methods read the sample that is being pointed to
float firstSampleAsFloat = pointer.getAsFloat();
int32 firstSampleAsInt = pointer.getAsInt32();
++pointer; // moves the pointer to the next sample.
pointer += 3; // skips the next 3 samples.
@endcode
The convertSamples() method lets you copy a range of samples from one format to another, automatically
converting its format.
@see AudioData::Converter
*/
template <typename SampleFormat,
typename Endianness,
typename InterleavingType,
typename Constness>
class Pointer : private InterleavingType // (inherited for EBCO)
{
public:
//==============================================================================
/** Creates a non-interleaved pointer from some raw data in the appropriate format.
This constructor is only used if you've specified the AudioData::NonInterleaved option -
for interleaved formats, use the constructor that also takes a number of channels.
*/
Pointer (typename Constness::VoidType* sourceData) noexcept
: data (Constness::toVoidPtr (sourceData))
{
// If you're using interleaved data, call the other constructor! If you're using non-interleaved data,
// you should pass NonInterleaved as the template parameter for the interleaving type!
static_jassert (InterleavingType::isInterleavedType == 0);
}
/** Creates a pointer from some raw data in the appropriate format with the specified number of interleaved channels.
For non-interleaved data, use the other constructor.
*/
Pointer (typename Constness::VoidType* sourceData, int numInterleaved) noexcept
: InterleavingType (numInterleaved), data (Constness::toVoidPtr (sourceData))
{
}
/** Creates a copy of another pointer. */
Pointer (const Pointer& other) noexcept
: InterleavingType (other), data (other.data)
{
}
Pointer& operator= (const Pointer& other) noexcept
{
InterleavingType::operator= (other);
data = other.data;
return *this;
}
//==============================================================================
/** Returns the value of the first sample as a floating point value.
The value will be in the range -1.0 to 1.0 for integer formats. For floating point
formats, the value could be outside that range, although -1 to 1 is the standard range.
*/
inline float getAsFloat() const noexcept { return Endianness::getAsFloat (data); }
/** Sets the value of the first sample as a floating point value.
(This method can only be used if the AudioData::NonConst option was used).
The value should be in the range -1.0 to 1.0 - for integer formats, values outside that
range will be clipped. For floating point formats, any value passed in here will be
written directly, although -1 to 1 is the standard range.
*/
inline void setAsFloat (float newValue) noexcept
{
static_jassert (Constness::isConst == 0); // trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead!
Endianness::setAsFloat (data, newValue);
}
/** Returns the value of the first sample as a 32-bit integer.
The value returned will be in the range 0x80000000 to 0x7fffffff, and shorter values will be
shifted to fill this range (e.g. if you're reading from 24-bit data, the values will be shifted up
by 8 bits when returned here). If the source data is floating point, values beyond -1.0 to 1.0 will
be clipped so that -1.0 maps onto -0x7fffffff and 1.0 maps to 0x7fffffff.
*/
inline int32 getAsInt32() const noexcept { return Endianness::getAsInt32 (data); }
/** Sets the value of the first sample as a 32-bit integer.
This will be mapped to the range of the format that is being written - see getAsInt32().
*/
inline void setAsInt32 (int32 newValue) noexcept
{
static_jassert (Constness::isConst == 0); // trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead!
Endianness::setAsInt32 (data, newValue);
}
/** Moves the pointer along to the next sample. */
inline Pointer& operator++() noexcept { advance(); return *this; }
/** Moves the pointer back to the previous sample. */
inline Pointer& operator--() noexcept { this->advanceDataBy (data, -1); return *this; }
/** Adds a number of samples to the pointer's position. */
Pointer& operator+= (int samplesToJump) noexcept { this->advanceDataBy (data, samplesToJump); return *this; }
/** Writes a stream of samples into this pointer from another pointer.
This will copy the specified number of samples, converting between formats appropriately.
*/
void convertSamples (Pointer source, int numSamples) const noexcept
{
static_jassert (Constness::isConst == 0); // trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead!
for (Pointer dest (*this); --numSamples >= 0;)
{
dest.data.copyFromSameType (source.data);
dest.advance();
source.advance();
}
}
/** Writes a stream of samples into this pointer from another pointer.
This will copy the specified number of samples, converting between formats appropriately.
*/
template <class OtherPointerType>
void convertSamples (OtherPointerType source, int numSamples) const noexcept
{
static_jassert (Constness::isConst == 0); // trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead!
Pointer dest (*this);
if (source.getRawData() != getRawData() || source.getNumBytesBetweenSamples() >= getNumBytesBetweenSamples())
{
while (--numSamples >= 0)
{
Endianness::copyFrom (dest.data, source);
dest.advance();
++source;
}
}
else // copy backwards if we're increasing the sample width..
{
dest += numSamples;
source += numSamples;
while (--numSamples >= 0)
Endianness::copyFrom ((--dest).data, --source);
}
}
/** Sets a number of samples to zero. */
void clearSamples (int numSamples) const noexcept
{
Pointer dest (*this);
dest.clear (dest.data, numSamples);
}
/** Scans a block of data, returning the lowest and highest levels as floats */
void findMinAndMax (size_t numSamples, float& minValue, float& maxValue) const noexcept
{
if (numSamples == 0)
{
minValue = maxValue = 0;
return;
}
Pointer dest (*this);
if (isFloatingPoint())
{
float mn = dest.getAsFloat();
dest.advance();
float mx = mn;
while (--numSamples > 0)
{
const float v = dest.getAsFloat();
dest.advance();
if (mx < v) mx = v;
if (v < mn) mn = v;
}
minValue = mn;
maxValue = mx;
}
else
{
int32 mn = dest.getAsInt32();
dest.advance();
int32 mx = mn;
while (--numSamples > 0)
{
const int v = dest.getAsInt32();
dest.advance();
if (mx < v) mx = v;
if (v < mn) mn = v;
}
minValue = mn * (float) (1.0 / (1.0 + Int32::maxValue));
maxValue = mx * (float) (1.0 / (1.0 + Int32::maxValue));
}
}
/** Returns true if the pointer is using a floating-point format. */
static bool isFloatingPoint() noexcept { return (bool) SampleFormat::isFloat; }
/** Returns true if the format is big-endian. */
static bool isBigEndian() noexcept { return (bool) Endianness::isBigEndian; }
/** Returns the number of bytes in each sample (ignoring the number of interleaved channels). */
static int getBytesPerSample() noexcept { return (int) SampleFormat::bytesPerSample; }
/** Returns the number of interleaved channels in the format. */
int getNumInterleavedChannels() const noexcept { return (int) this->numInterleavedChannels; }
/** Returns the number of bytes between the start address of each sample. */
int getNumBytesBetweenSamples() const noexcept { return InterleavingType::getNumBytesBetweenSamples (data); }
/** Returns the accuracy of this format when represented as a 32-bit integer.
This is the smallest number above 0 that can be represented in the sample format, converted to
a 32-bit range. E,g. if the format is 8-bit, its resolution is 0x01000000; if the format is 24-bit,
its resolution is 0x100.
*/
static int get32BitResolution() noexcept { return (int) SampleFormat::resolution; }
/** Returns a pointer to the underlying data. */
const void* getRawData() const noexcept { return data.data; }
private:
//==============================================================================
SampleFormat data;
inline void advance() noexcept { this->advanceData (data); }
Pointer operator++ (int); // private to force you to use the more efficient pre-increment!
Pointer operator-- (int);
};
//==============================================================================
/** A base class for objects that are used to convert between two different sample formats.
The AudioData::ConverterInstance implements this base class and can be templated, so
you can create an instance that converts between two particular formats, and then
store this in the abstract base class.
@see AudioData::ConverterInstance
*/
class Converter
{
public:
virtual ~Converter() {}
/** Converts a sequence of samples from the converter's source format into the dest format. */
virtual void convertSamples (void* destSamples, const void* sourceSamples, int numSamples) const = 0;
/** Converts a sequence of samples from the converter's source format into the dest format.
This method takes sub-channel indexes, which can be used with interleaved formats in order to choose a
particular sub-channel of the data to be used.
*/
virtual void convertSamples (void* destSamples, int destSubChannel,
const void* sourceSamples, int sourceSubChannel, int numSamples) const = 0;
};
//==============================================================================
/**
A class that converts between two templated AudioData::Pointer types, and which
implements the AudioData::Converter interface.
This can be used as a concrete instance of the AudioData::Converter abstract class.
@see AudioData::Converter
*/
template <class SourceSampleType, class DestSampleType>
class ConverterInstance : public Converter
{
public:
ConverterInstance (int numSourceChannels = 1, int numDestChannels = 1)
: sourceChannels (numSourceChannels), destChannels (numDestChannels)
{}
~ConverterInstance() {}
void convertSamples (void* dest, const void* source, int numSamples) const
{
SourceSampleType s (source, sourceChannels);
DestSampleType d (dest, destChannels);
d.convertSamples (s, numSamples);
}
void convertSamples (void* dest, int destSubChannel,
const void* source, int sourceSubChannel, int numSamples) const
{
jassert (destSubChannel < destChannels && sourceSubChannel < sourceChannels);
SourceSampleType s (addBytesToPointer (source, sourceSubChannel * SourceSampleType::getBytesPerSample()), sourceChannels);
DestSampleType d (addBytesToPointer (dest, destSubChannel * DestSampleType::getBytesPerSample()), destChannels);
d.convertSamples (s, numSamples);
}
private:
JUCE_DECLARE_NON_COPYABLE (ConverterInstance)
const int sourceChannels, destChannels;
};
};
//==============================================================================
/**
A set of routines to convert buffers of 32-bit floating point data to and from
various integer formats.
Note that these functions are deprecated - the AudioData class provides a much more
flexible set of conversion classes now.
*/
class JUCE_API AudioDataConverters
{
public:
//==============================================================================
static void convertFloatToInt16LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 2);
static void convertFloatToInt16BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 2);
static void convertFloatToInt24LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 3);
static void convertFloatToInt24BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 3);
static void convertFloatToInt32LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4);
static void convertFloatToInt32BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4);
static void convertFloatToFloat32LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4);
static void convertFloatToFloat32BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4);
//==============================================================================
static void convertInt16LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 2);
static void convertInt16BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 2);
static void convertInt24LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 3);
static void convertInt24BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 3);
static void convertInt32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4);
static void convertInt32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4);
static void convertFloat32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4);
static void convertFloat32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4);
//==============================================================================
enum DataFormat
{
int16LE,
int16BE,
int24LE,
int24BE,
int32LE,
int32BE,
float32LE,
float32BE,
};
static void convertFloatToFormat (DataFormat destFormat,
const float* source, void* dest, int numSamples);
static void convertFormatToFloat (DataFormat sourceFormat,
const void* source, float* dest, int numSamples);
//==============================================================================
static void interleaveSamples (const float** source, float* dest,
int numSamples, int numChannels);
static void deinterleaveSamples (const float* source, float** dest,
int numSamples, int numChannels);
private:
AudioDataConverters();
JUCE_DECLARE_NON_COPYABLE (AudioDataConverters)
};
#endif // JUCE_AUDIODATACONVERTERS_H_INCLUDED

+ 542
- 0
source/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.cpp View File

@@ -0,0 +1,542 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
AudioSampleBuffer::AudioSampleBuffer (const int numChans,
const int numSamples) noexcept
: numChannels (numChans),
size (numSamples)
{
jassert (numSamples >= 0);
jassert (numChans > 0);
allocateData();
}
AudioSampleBuffer::AudioSampleBuffer (const AudioSampleBuffer& other) noexcept
: numChannels (other.numChannels),
size (other.size),
allocatedBytes (other.allocatedBytes)
{
if (allocatedBytes == 0)
{
allocateChannels (other.channels, 0);
}
else
{
allocateData();
for (int i = 0; i < numChannels; ++i)
FloatVectorOperations::copy (channels[i], other.channels[i], size);
}
}
void AudioSampleBuffer::allocateData()
{
const size_t channelListSize = sizeof (float*) * (size_t) (numChannels + 1);
allocatedBytes = (size_t) numChannels * (size_t) size * sizeof (float) + channelListSize + 32;
allocatedData.malloc (allocatedBytes);
channels = reinterpret_cast <float**> (allocatedData.getData());
float* chan = (float*) (allocatedData + channelListSize);
for (int i = 0; i < numChannels; ++i)
{
channels[i] = chan;
chan += size;
}
channels [numChannels] = nullptr;
}
AudioSampleBuffer::AudioSampleBuffer (float* const* dataToReferTo,
const int numChans,
const int numSamples) noexcept
: numChannels (numChans),
size (numSamples),
allocatedBytes (0)
{
jassert (numChans > 0);
allocateChannels (dataToReferTo, 0);
}
AudioSampleBuffer::AudioSampleBuffer (float* const* dataToReferTo,
const int numChans,
const int startSample,
const int numSamples) noexcept
: numChannels (numChans),
size (numSamples),
allocatedBytes (0)
{
jassert (numChans > 0);
allocateChannels (dataToReferTo, startSample);
}
void AudioSampleBuffer::setDataToReferTo (float** dataToReferTo,
const int newNumChannels,
const int newNumSamples) noexcept
{
jassert (newNumChannels > 0);
allocatedBytes = 0;
allocatedData.free();
numChannels = newNumChannels;
size = newNumSamples;
allocateChannels (dataToReferTo, 0);
}
void AudioSampleBuffer::allocateChannels (float* const* const dataToReferTo, int offset)
{
// (try to avoid doing a malloc here, as that'll blow up things like Pro-Tools)
if (numChannels < (int) numElementsInArray (preallocatedChannelSpace))
{
channels = static_cast <float**> (preallocatedChannelSpace);
}
else
{
allocatedData.malloc ((size_t) numChannels + 1, sizeof (float*));
channels = reinterpret_cast <float**> (allocatedData.getData());
}
for (int i = 0; i < numChannels; ++i)
{
// you have to pass in the same number of valid pointers as numChannels
jassert (dataToReferTo[i] != nullptr);
channels[i] = dataToReferTo[i] + offset;
}
channels [numChannels] = nullptr;
}
AudioSampleBuffer& AudioSampleBuffer::operator= (const AudioSampleBuffer& other) noexcept
{
if (this != &other)
{
setSize (other.getNumChannels(), other.getNumSamples(), false, false, false);
for (int i = 0; i < numChannels; ++i)
FloatVectorOperations::copy (channels[i], other.channels[i], size);
}
return *this;
}
AudioSampleBuffer::~AudioSampleBuffer() noexcept
{
}
void AudioSampleBuffer::setSize (const int newNumChannels,
const int newNumSamples,
const bool keepExistingContent,
const bool clearExtraSpace,
const bool avoidReallocating) noexcept
{
jassert (newNumChannels > 0);
jassert (newNumSamples >= 0);
if (newNumSamples != size || newNumChannels != numChannels)
{
const size_t allocatedSamplesPerChannel = (newNumSamples + 3) & ~3;
const size_t channelListSize = ((sizeof (float*) * (size_t) (newNumChannels + 1)) + 15) & ~15;
const size_t newTotalBytes = ((size_t) newNumChannels * (size_t) allocatedSamplesPerChannel * sizeof (float))
+ channelListSize + 32;
if (keepExistingContent)
{
HeapBlock <char, true> newData;
newData.allocate (newTotalBytes, clearExtraSpace);
const size_t numSamplesToCopy = jmin (newNumSamples, size);
float** const newChannels = reinterpret_cast <float**> (newData.getData());
float* newChan = reinterpret_cast <float*> (newData + channelListSize);
for (int j = 0; j < newNumChannels; ++j)
{
newChannels[j] = newChan;
newChan += allocatedSamplesPerChannel;
}
const int numChansToCopy = jmin (numChannels, newNumChannels);
for (int i = 0; i < numChansToCopy; ++i)
FloatVectorOperations::copy (newChannels[i], channels[i], (int) numSamplesToCopy);
allocatedData.swapWith (newData);
allocatedBytes = newTotalBytes;
channels = newChannels;
}
else
{
if (avoidReallocating && allocatedBytes >= newTotalBytes)
{
if (clearExtraSpace)
allocatedData.clear (newTotalBytes);
}
else
{
allocatedBytes = newTotalBytes;
allocatedData.allocate (newTotalBytes, clearExtraSpace);
channels = reinterpret_cast <float**> (allocatedData.getData());
}
float* chan = reinterpret_cast <float*> (allocatedData + channelListSize);
for (int i = 0; i < newNumChannels; ++i)
{
channels[i] = chan;
chan += allocatedSamplesPerChannel;
}
}
channels [newNumChannels] = 0;
size = newNumSamples;
numChannels = newNumChannels;
}
}
void AudioSampleBuffer::clear() noexcept
{
for (int i = 0; i < numChannels; ++i)
FloatVectorOperations::clear (channels[i], size);
}
void AudioSampleBuffer::clear (const int startSample,
const int numSamples) noexcept
{
jassert (startSample >= 0 && startSample + numSamples <= size);
for (int i = 0; i < numChannels; ++i)
FloatVectorOperations::clear (channels[i] + startSample, numSamples);
}
void AudioSampleBuffer::clear (const int channel,
const int startSample,
const int numSamples) noexcept
{
jassert (isPositiveAndBelow (channel, numChannels));
jassert (startSample >= 0 && startSample + numSamples <= size);
FloatVectorOperations::clear (channels [channel] + startSample, numSamples);
}
void AudioSampleBuffer::applyGain (const int channel,
const int startSample,
int numSamples,
const float gain) noexcept
{
jassert (isPositiveAndBelow (channel, numChannels));
jassert (startSample >= 0 && startSample + numSamples <= size);
if (gain != 1.0f)
{
float* const d = channels [channel] + startSample;
if (gain == 0.0f)
FloatVectorOperations::clear (d, numSamples);
else
FloatVectorOperations::multiply (d, gain, numSamples);
}
}
void AudioSampleBuffer::applyGainRamp (const int channel,
const int startSample,
int numSamples,
float startGain,
float endGain) noexcept
{
if (startGain == endGain)
{
applyGain (channel, startSample, numSamples, startGain);
}
else
{
jassert (isPositiveAndBelow (channel, numChannels));
jassert (startSample >= 0 && startSample + numSamples <= size);
const float increment = (endGain - startGain) / numSamples;
float* d = channels [channel] + startSample;
while (--numSamples >= 0)
{
*d++ *= startGain;
startGain += increment;
}
}
}
void AudioSampleBuffer::applyGain (const int startSample,
const int numSamples,
const float gain) noexcept
{
for (int i = 0; i < numChannels; ++i)
applyGain (i, startSample, numSamples, gain);
}
void AudioSampleBuffer::applyGain (const float gain) noexcept
{
applyGain (0, size, gain);
}
void AudioSampleBuffer::applyGainRamp (const int startSample,
const int numSamples,
const float startGain,
const float endGain) noexcept
{
for (int i = 0; i < numChannels; ++i)
applyGainRamp (i, startSample, numSamples, startGain, endGain);
}
void AudioSampleBuffer::addFrom (const int destChannel,
const int destStartSample,
const AudioSampleBuffer& source,
const int sourceChannel,
const int sourceStartSample,
int numSamples,
const float gain) noexcept
{
jassert (&source != this || sourceChannel != destChannel);
jassert (isPositiveAndBelow (destChannel, numChannels));
jassert (destStartSample >= 0 && destStartSample + numSamples <= size);
jassert (isPositiveAndBelow (sourceChannel, source.numChannels));
jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size);
if (gain != 0.0f && numSamples > 0)
{
float* const d = channels [destChannel] + destStartSample;
const float* const s = source.channels [sourceChannel] + sourceStartSample;
if (gain != 1.0f)
FloatVectorOperations::addWithMultiply (d, s, gain, numSamples);
else
FloatVectorOperations::add (d, s, numSamples);
}
}
void AudioSampleBuffer::addFrom (const int destChannel,
const int destStartSample,
const float* source,
int numSamples,
const float gain) noexcept
{
jassert (isPositiveAndBelow (destChannel, numChannels));
jassert (destStartSample >= 0 && destStartSample + numSamples <= size);
jassert (source != nullptr);
if (gain != 0.0f && numSamples > 0)
{
float* const d = channels [destChannel] + destStartSample;
if (gain != 1.0f)
FloatVectorOperations::addWithMultiply (d, source, gain, numSamples);
else
FloatVectorOperations::add (d, source, numSamples);
}
}
void AudioSampleBuffer::addFromWithRamp (const int destChannel,
const int destStartSample,
const float* source,
int numSamples,
float startGain,
const float endGain) noexcept
{
jassert (isPositiveAndBelow (destChannel, numChannels));
jassert (destStartSample >= 0 && destStartSample + numSamples <= size);
jassert (source != nullptr);
if (startGain == endGain)
{
addFrom (destChannel, destStartSample, source, numSamples, startGain);
}
else
{
if (numSamples > 0 && (startGain != 0.0f || endGain != 0.0f))
{
const float increment = (endGain - startGain) / numSamples;
float* d = channels [destChannel] + destStartSample;
while (--numSamples >= 0)
{
*d++ += startGain * *source++;
startGain += increment;
}
}
}
}
void AudioSampleBuffer::copyFrom (const int destChannel,
const int destStartSample,
const AudioSampleBuffer& source,
const int sourceChannel,
const int sourceStartSample,
int numSamples) noexcept
{
jassert (&source != this || sourceChannel != destChannel);
jassert (isPositiveAndBelow (destChannel, numChannels));
jassert (destStartSample >= 0 && destStartSample + numSamples <= size);
jassert (isPositiveAndBelow (sourceChannel, source.numChannels));
jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size);
if (numSamples > 0)
{
FloatVectorOperations::copy (channels [destChannel] + destStartSample,
source.channels [sourceChannel] + sourceStartSample,
numSamples);
}
}
void AudioSampleBuffer::copyFrom (const int destChannel,
const int destStartSample,
const float* source,
int numSamples) noexcept
{
jassert (isPositiveAndBelow (destChannel, numChannels));
jassert (destStartSample >= 0 && destStartSample + numSamples <= size);
jassert (source != nullptr);
if (numSamples > 0)
{
FloatVectorOperations::copy (channels [destChannel] + destStartSample,
source,
numSamples);
}
}
void AudioSampleBuffer::copyFrom (const int destChannel,
const int destStartSample,
const float* source,
int numSamples,
const float gain) noexcept
{
jassert (isPositiveAndBelow (destChannel, numChannels));
jassert (destStartSample >= 0 && destStartSample + numSamples <= size);
jassert (source != nullptr);
if (numSamples > 0)
{
float* d = channels [destChannel] + destStartSample;
if (gain != 1.0f)
{
if (gain == 0)
FloatVectorOperations::clear (d, numSamples);
else
FloatVectorOperations::copyWithMultiply (d, source, gain, numSamples);
}
else
{
FloatVectorOperations::copy (d, source, numSamples);
}
}
}
void AudioSampleBuffer::copyFromWithRamp (const int destChannel,
const int destStartSample,
const float* source,
int numSamples,
float startGain,
float endGain) noexcept
{
jassert (isPositiveAndBelow (destChannel, numChannels));
jassert (destStartSample >= 0 && destStartSample + numSamples <= size);
jassert (source != nullptr);
if (startGain == endGain)
{
copyFrom (destChannel, destStartSample, source, numSamples, startGain);
}
else
{
if (numSamples > 0 && (startGain != 0.0f || endGain != 0.0f))
{
const float increment = (endGain - startGain) / numSamples;
float* d = channels [destChannel] + destStartSample;
while (--numSamples >= 0)
{
*d++ = startGain * *source++;
startGain += increment;
}
}
}
}
void AudioSampleBuffer::findMinMax (const int channel,
const int startSample,
int numSamples,
float& minVal,
float& maxVal) const noexcept
{
jassert (isPositiveAndBelow (channel, numChannels));
jassert (startSample >= 0 && startSample + numSamples <= size);
FloatVectorOperations::findMinAndMax (channels [channel] + startSample,
numSamples, minVal, maxVal);
}
float AudioSampleBuffer::getMagnitude (const int channel,
const int startSample,
const int numSamples) const noexcept
{
jassert (isPositiveAndBelow (channel, numChannels));
jassert (startSample >= 0 && startSample + numSamples <= size);
float mn, mx;
findMinMax (channel, startSample, numSamples, mn, mx);
return jmax (mn, -mn, mx, -mx);
}
float AudioSampleBuffer::getMagnitude (const int startSample,
const int numSamples) const noexcept
{
float mag = 0.0f;
for (int i = 0; i < numChannels; ++i)
mag = jmax (mag, getMagnitude (i, startSample, numSamples));
return mag;
}
float AudioSampleBuffer::getRMSLevel (const int channel,
const int startSample,
const int numSamples) const noexcept
{
jassert (isPositiveAndBelow (channel, numChannels));
jassert (startSample >= 0 && startSample + numSamples <= size);
if (numSamples <= 0 || channel < 0 || channel >= numChannels)
return 0.0f;
const float* const data = channels [channel] + startSample;
double sum = 0.0;
for (int i = 0; i < numSamples; ++i)
{
const float sample = data [i];
sum += sample * sample;
}
return (float) std::sqrt (sum / numSamples);
}

+ 444
- 0
source/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h View File

@@ -0,0 +1,444 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_AUDIOSAMPLEBUFFER_H_INCLUDED
#define JUCE_AUDIOSAMPLEBUFFER_H_INCLUDED
//==============================================================================
/**
A multi-channel buffer of 32-bit floating point audio samples.
*/
class JUCE_API AudioSampleBuffer
{
public:
//==============================================================================
/** Creates a buffer with a specified number of channels and samples.
The contents of the buffer will initially be undefined, so use clear() to
set all the samples to zero.
The buffer will allocate its memory internally, and this will be released
when the buffer is deleted. If the memory can't be allocated, this will
throw a std::bad_alloc exception.
*/
AudioSampleBuffer (int numChannels,
int numSamples) noexcept;
/** Creates a buffer using a pre-allocated block of memory.
Note that if the buffer is resized or its number of channels is changed, it
will re-allocate memory internally and copy the existing data to this new area,
so it will then stop directly addressing this memory.
@param dataToReferTo a pre-allocated array containing pointers to the data
for each channel that should be used by this buffer. The
buffer will only refer to this memory, it won't try to delete
it when the buffer is deleted or resized.
@param numChannels the number of channels to use - this must correspond to the
number of elements in the array passed in
@param numSamples the number of samples to use - this must correspond to the
size of the arrays passed in
*/
AudioSampleBuffer (float* const* dataToReferTo,
int numChannels,
int numSamples) noexcept;
/** Creates a buffer using a pre-allocated block of memory.
Note that if the buffer is resized or its number of channels is changed, it
will re-allocate memory internally and copy the existing data to this new area,
so it will then stop directly addressing this memory.
@param dataToReferTo a pre-allocated array containing pointers to the data
for each channel that should be used by this buffer. The
buffer will only refer to this memory, it won't try to delete
it when the buffer is deleted or resized.
@param numChannels the number of channels to use - this must correspond to the
number of elements in the array passed in
@param startSample the offset within the arrays at which the data begins
@param numSamples the number of samples to use - this must correspond to the
size of the arrays passed in
*/
AudioSampleBuffer (float* const* dataToReferTo,
int numChannels,
int startSample,
int numSamples) noexcept;
/** Copies another buffer.
This buffer will make its own copy of the other's data, unless the buffer was created
using an external data buffer, in which case boths buffers will just point to the same
shared block of data.
*/
AudioSampleBuffer (const AudioSampleBuffer& other) noexcept;
/** Copies another buffer onto this one.
This buffer's size will be changed to that of the other buffer.
*/
AudioSampleBuffer& operator= (const AudioSampleBuffer& other) noexcept;
/** Destructor.
This will free any memory allocated by the buffer.
*/
~AudioSampleBuffer() noexcept;
//==============================================================================
/** Returns the number of channels of audio data that this buffer contains.
@see getSampleData
*/
int getNumChannels() const noexcept { return numChannels; }
/** Returns the number of samples allocated in each of the buffer's channels.
@see getSampleData
*/
int getNumSamples() const noexcept { return size; }
/** Returns a pointer one of the buffer's channels.
For speed, this doesn't check whether the channel number is out of range,
so be careful when using it!
*/
float* getSampleData (const int channelNumber) const noexcept
{
jassert (isPositiveAndBelow (channelNumber, numChannels));
return channels [channelNumber];
}
/** Returns a pointer to a sample in one of the buffer's channels.
For speed, this doesn't check whether the channel and sample number
are out-of-range, so be careful when using it!
*/
float* getSampleData (const int channelNumber,
const int sampleOffset) const noexcept
{
jassert (isPositiveAndBelow (channelNumber, numChannels));
jassert (isPositiveAndBelow (sampleOffset, size));
return channels [channelNumber] + sampleOffset;
}
/** Returns an array of pointers to the channels in the buffer.
Don't modify any of the pointers that are returned, and bear in mind that
these will become invalid if the buffer is resized.
*/
float** getArrayOfChannels() const noexcept { return channels; }
//==============================================================================
/** Changes the buffer's size or number of channels.
This can expand or contract the buffer's length, and add or remove channels.
If keepExistingContent is true, it will try to preserve as much of the
old data as it can in the new buffer.
If clearExtraSpace is true, then any extra channels or space that is
allocated will be also be cleared. If false, then this space is left
uninitialised.
If avoidReallocating is true, then changing the buffer's size won't reduce the
amount of memory that is currently allocated (but it will still increase it if
the new size is bigger than the amount it currently has). If this is false, then
a new allocation will be done so that the buffer uses takes up the minimum amount
of memory that it needs.
If the required memory can't be allocated, this will throw a std::bad_alloc exception.
*/
void setSize (int newNumChannels,
int newNumSamples,
bool keepExistingContent = false,
bool clearExtraSpace = false,
bool avoidReallocating = false) noexcept;
/** Makes this buffer point to a pre-allocated set of channel data arrays.
There's also a constructor that lets you specify arrays like this, but this
lets you change the channels dynamically.
Note that if the buffer is resized or its number of channels is changed, it
will re-allocate memory internally and copy the existing data to this new area,
so it will then stop directly addressing this memory.
@param dataToReferTo a pre-allocated array containing pointers to the data
for each channel that should be used by this buffer. The
buffer will only refer to this memory, it won't try to delete
it when the buffer is deleted or resized.
@param numChannels the number of channels to use - this must correspond to the
number of elements in the array passed in
@param numSamples the number of samples to use - this must correspond to the
size of the arrays passed in
*/
void setDataToReferTo (float** dataToReferTo,
int numChannels,
int numSamples) noexcept;
//==============================================================================
/** Clears all the samples in all channels. */
void clear() noexcept;
/** Clears a specified region of all the channels.
For speed, this doesn't check whether the channel and sample number
are in-range, so be careful!
*/
void clear (int startSample,
int numSamples) noexcept;
/** Clears a specified region of just one channel.
For speed, this doesn't check whether the channel and sample number
are in-range, so be careful!
*/
void clear (int channel,
int startSample,
int numSamples) noexcept;
/** Applies a gain multiple to a region of one channel.
For speed, this doesn't check whether the channel and sample number
are in-range, so be careful!
*/
void applyGain (int channel,
int startSample,
int numSamples,
float gain) noexcept;
/** Applies a gain multiple to a region of all the channels.
For speed, this doesn't check whether the sample numbers
are in-range, so be careful!
*/
void applyGain (int startSample,
int numSamples,
float gain) noexcept;
/** Applies a gain multiple to all the audio data. */
void applyGain (float gain) noexcept;
/** Applies a range of gains to a region of a channel.
The gain that is applied to each sample will vary from
startGain on the first sample to endGain on the last Sample,
so it can be used to do basic fades.
For speed, this doesn't check whether the sample numbers
are in-range, so be careful!
*/
void applyGainRamp (int channel,
int startSample,
int numSamples,
float startGain,
float endGain) noexcept;
/** Applies a range of gains to a region of all channels.
The gain that is applied to each sample will vary from
startGain on the first sample to endGain on the last Sample,
so it can be used to do basic fades.
For speed, this doesn't check whether the sample numbers
are in-range, so be careful!
*/
void applyGainRamp (int startSample,
int numSamples,
float startGain,
float endGain) noexcept;
/** Adds samples from another buffer to this one.
@param destChannel the channel within this buffer to add the samples to
@param destStartSample the start sample within this buffer's channel
@param source the source buffer to add from
@param sourceChannel the channel within the source buffer to read from
@param sourceStartSample the offset within the source buffer's channel to start reading samples from
@param numSamples the number of samples to process
@param gainToApplyToSource an optional gain to apply to the source samples before they are
added to this buffer's samples
@see copyFrom
*/
void addFrom (int destChannel,
int destStartSample,
const AudioSampleBuffer& source,
int sourceChannel,
int sourceStartSample,
int numSamples,
float gainToApplyToSource = 1.0f) noexcept;
/** Adds samples from an array of floats to one of the channels.
@param destChannel the channel within this buffer to add the samples to
@param destStartSample the start sample within this buffer's channel
@param source the source data to use
@param numSamples the number of samples to process
@param gainToApplyToSource an optional gain to apply to the source samples before they are
added to this buffer's samples
@see copyFrom
*/
void addFrom (int destChannel,
int destStartSample,
const float* source,
int numSamples,
float gainToApplyToSource = 1.0f) noexcept;
/** Adds samples from an array of floats, applying a gain ramp to them.
@param destChannel the channel within this buffer to add the samples to
@param destStartSample the start sample within this buffer's channel
@param source the source data to use
@param numSamples the number of samples to process
@param startGain the gain to apply to the first sample (this is multiplied with
the source samples before they are added to this buffer)
@param endGain the gain to apply to the final sample. The gain is linearly
interpolated between the first and last samples.
*/
void addFromWithRamp (int destChannel,
int destStartSample,
const float* source,
int numSamples,
float startGain,
float endGain) noexcept;
/** Copies samples from another buffer to this one.
@param destChannel the channel within this buffer to copy the samples to
@param destStartSample the start sample within this buffer's channel
@param source the source buffer to read from
@param sourceChannel the channel within the source buffer to read from
@param sourceStartSample the offset within the source buffer's channel to start reading samples from
@param numSamples the number of samples to process
@see addFrom
*/
void copyFrom (int destChannel,
int destStartSample,
const AudioSampleBuffer& source,
int sourceChannel,
int sourceStartSample,
int numSamples) noexcept;
/** Copies samples from an array of floats into one of the channels.
@param destChannel the channel within this buffer to copy the samples to
@param destStartSample the start sample within this buffer's channel
@param source the source buffer to read from
@param numSamples the number of samples to process
@see addFrom
*/
void copyFrom (int destChannel,
int destStartSample,
const float* source,
int numSamples) noexcept;
/** Copies samples from an array of floats into one of the channels, applying a gain to it.
@param destChannel the channel within this buffer to copy the samples to
@param destStartSample the start sample within this buffer's channel
@param source the source buffer to read from
@param numSamples the number of samples to process
@param gain the gain to apply
@see addFrom
*/
void copyFrom (int destChannel,
int destStartSample,
const float* source,
int numSamples,
float gain) noexcept;
/** Copies samples from an array of floats into one of the channels, applying a gain ramp.
@param destChannel the channel within this buffer to copy the samples to
@param destStartSample the start sample within this buffer's channel
@param source the source buffer to read from
@param numSamples the number of samples to process
@param startGain the gain to apply to the first sample (this is multiplied with
the source samples before they are copied to this buffer)
@param endGain the gain to apply to the final sample. The gain is linearly
interpolated between the first and last samples.
@see addFrom
*/
void copyFromWithRamp (int destChannel,
int destStartSample,
const float* source,
int numSamples,
float startGain,
float endGain) noexcept;
/** Finds the highest and lowest sample values in a given range.
@param channel the channel to read from
@param startSample the start sample within the channel
@param numSamples the number of samples to check
@param minVal on return, the lowest value that was found
@param maxVal on return, the highest value that was found
*/
void findMinMax (int channel,
int startSample,
int numSamples,
float& minVal,
float& maxVal) const noexcept;
/** Finds the highest absolute sample value within a region of a channel.
*/
float getMagnitude (int channel,
int startSample,
int numSamples) const noexcept;
/** Finds the highest absolute sample value within a region on all channels.
*/
float getMagnitude (int startSample,
int numSamples) const noexcept;
/** Returns the root mean squared level for a region of a channel.
*/
float getRMSLevel (int channel,
int startSample,
int numSamples) const noexcept;
private:
//==============================================================================
int numChannels, size;
size_t allocatedBytes;
float** channels;
HeapBlock <char, true> allocatedData;
float* preallocatedChannelSpace [32];
void allocateData();
void allocateChannels (float* const* dataToReferTo, int offset);
JUCE_LEAK_DETECTOR (AudioSampleBuffer)
};
#endif // JUCE_AUDIOSAMPLEBUFFER_H_INCLUDED

+ 344
- 0
source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp View File

@@ -0,0 +1,344 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#if JUCE_USE_SSE_INTRINSICS
namespace FloatVectorHelpers
{
static bool sse2Present = false;
static bool isSSE2Available() noexcept
{
if (sse2Present)
return true;
sse2Present = SystemStats::hasSSE2();
return sse2Present;
}
inline static bool isAligned (const void* p) noexcept
{
return (((pointer_sized_int) p) & 15) == 0;
}
inline static void mmEmpty() noexcept
{
#if ! JUCE_64BIT
_mm_empty();
#endif
}
static inline float findMinimumOrMaximum (const float* src, int num, const bool isMinimum) noexcept
{
#if JUCE_USE_SSE_INTRINSICS
const int numLongOps = num / 4;
if (numLongOps > 1 && FloatVectorHelpers::isSSE2Available())
{
__m128 val;
#define JUCE_MINIMUMMAXIMUM_SSE_LOOP(loadOp, minMaxOp) \
val = loadOp (src); \
src += 4; \
for (int i = 1; i < numLongOps; ++i) \
{ \
const __m128 s = loadOp (src); \
val = minMaxOp (val, s); \
src += 4; \
}
if (isMinimum)
{
if (FloatVectorHelpers::isAligned (src)) { JUCE_MINIMUMMAXIMUM_SSE_LOOP (_mm_load_ps, _mm_min_ps) }
else { JUCE_MINIMUMMAXIMUM_SSE_LOOP (_mm_loadu_ps, _mm_min_ps) }
}
else
{
if (FloatVectorHelpers::isAligned (src)) { JUCE_MINIMUMMAXIMUM_SSE_LOOP (_mm_load_ps, _mm_max_ps) }
else { JUCE_MINIMUMMAXIMUM_SSE_LOOP (_mm_loadu_ps,_mm_max_ps) }
}
float localVal;
{
float vals[4];
_mm_storeu_ps (vals, val);
FloatVectorHelpers::mmEmpty();
localVal = isMinimum ? jmin (vals[0], vals[1], vals[2], vals[3])
: jmax (vals[0], vals[1], vals[2], vals[3]);
}
num &= 3;
for (int i = 0; i < num; ++i)
localVal = isMinimum ? jmin (localVal, src[i])
: jmax (localVal, src[i]);
return localVal;
}
#endif
return isMinimum ? juce::findMinimum (src, num)
: juce::findMaximum (src, num);
}
}
#define JUCE_BEGIN_SSE_OP \
if (FloatVectorHelpers::isSSE2Available()) \
{ \
const int numLongOps = num / 4;
#define JUCE_FINISH_SSE_OP(normalOp) \
FloatVectorHelpers::mmEmpty(); \
num &= 3; \
if (num == 0) return; \
} \
for (int i = 0; i < num; ++i) normalOp;
#define JUCE_SSE_LOOP(sseOp, srcLoad, dstLoad, dstStore, locals, increment) \
for (int i = 0; i < numLongOps; ++i) \
{ \
locals (srcLoad, dstLoad); \
dstStore (dest, sseOp); \
increment; \
}
#define JUCE_INCREMENT_SRC_DEST dest += 4; src += 4;
#define JUCE_INCREMENT_DEST dest += 4;
#define JUCE_LOAD_NONE(srcLoad, dstLoad)
#define JUCE_LOAD_DEST(srcLoad, dstLoad) const __m128 d = dstLoad (dest);
#define JUCE_LOAD_SRC(srcLoad, dstLoad) const __m128 s = srcLoad (src);
#define JUCE_LOAD_SRC_DEST(srcLoad, dstLoad) const __m128 d = dstLoad (dest); const __m128 s = srcLoad (src);
#define JUCE_PERFORM_SSE_OP_DEST(normalOp, sseOp, locals) \
JUCE_BEGIN_SSE_OP \
if (FloatVectorHelpers::isAligned (dest)) JUCE_SSE_LOOP (sseOp, dummy, _mm_load_ps, _mm_store_ps, locals, JUCE_INCREMENT_DEST) \
else JUCE_SSE_LOOP (sseOp, dummy, _mm_loadu_ps, _mm_storeu_ps, locals, JUCE_INCREMENT_DEST) \
JUCE_FINISH_SSE_OP (normalOp)
#define JUCE_PERFORM_SSE_OP_SRC_DEST(normalOp, sseOp, locals, increment) \
JUCE_BEGIN_SSE_OP \
if (FloatVectorHelpers::isAligned (dest)) \
{ \
if (FloatVectorHelpers::isAligned (src)) JUCE_SSE_LOOP (sseOp, _mm_load_ps, _mm_load_ps, _mm_store_ps, locals, increment) \
else JUCE_SSE_LOOP (sseOp, _mm_loadu_ps, _mm_load_ps, _mm_store_ps, locals, increment) \
}\
else \
{ \
if (FloatVectorHelpers::isAligned (src)) JUCE_SSE_LOOP (sseOp, _mm_load_ps, _mm_loadu_ps, _mm_storeu_ps, locals, increment) \
else JUCE_SSE_LOOP (sseOp, _mm_loadu_ps, _mm_loadu_ps, _mm_storeu_ps, locals, increment) \
} \
JUCE_FINISH_SSE_OP (normalOp)
#else
#define JUCE_PERFORM_SSE_OP_DEST(normalOp, unused1, unused2) for (int i = 0; i < num; ++i) normalOp;
#define JUCE_PERFORM_SSE_OP_SRC_DEST(normalOp, sseOp, locals, increment) for (int i = 0; i < num; ++i) normalOp;
#endif
void JUCE_CALLTYPE FloatVectorOperations::clear (float* dest, int num) noexcept
{
#if JUCE_USE_VDSP_FRAMEWORK
vDSP_vclr (dest, 1, num);
#else
zeromem (dest, num * sizeof (float));
#endif
}
void JUCE_CALLTYPE FloatVectorOperations::fill (float* dest, float valueToFill, int num) noexcept
{
#if JUCE_USE_VDSP_FRAMEWORK
vDSP_vfill (&valueToFill, dest, 1, num);
#else
#if JUCE_USE_SSE_INTRINSICS
const __m128 val = _mm_load1_ps (&valueToFill);
#endif
JUCE_PERFORM_SSE_OP_DEST (dest[i] = valueToFill, val, JUCE_LOAD_NONE)
#endif
}
void JUCE_CALLTYPE FloatVectorOperations::copy (float* dest, const float* src, int num) noexcept
{
memcpy (dest, src, num * sizeof (float));
}
void JUCE_CALLTYPE FloatVectorOperations::copyWithMultiply (float* dest, const float* src, float multiplier, int num) noexcept
{
#if JUCE_USE_VDSP_FRAMEWORK
vDSP_vsmul (src, 1, &multiplier, dest, 1, num);
#else
#if JUCE_USE_SSE_INTRINSICS
const __m128 mult = _mm_load1_ps (&multiplier);
#endif
JUCE_PERFORM_SSE_OP_SRC_DEST (dest[i] = src[i] * multiplier,
_mm_mul_ps (mult, s),
JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST)
#endif
}
void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, const float* src, int num) noexcept
{
#if JUCE_USE_VDSP_FRAMEWORK
vDSP_vadd (src, 1, dest, 1, dest, 1, num);
#else
JUCE_PERFORM_SSE_OP_SRC_DEST (dest[i] += src[i],
_mm_add_ps (d, s),
JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST)
#endif
}
void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, float amount, int num) noexcept
{
#if JUCE_USE_SSE_INTRINSICS
const __m128 amountToAdd = _mm_load1_ps (&amount);
#endif
JUCE_PERFORM_SSE_OP_DEST (dest[i] += amount,
_mm_add_ps (d, amountToAdd),
JUCE_LOAD_DEST)
}
void JUCE_CALLTYPE FloatVectorOperations::addWithMultiply (float* dest, const float* src, float multiplier, int num) noexcept
{
#if JUCE_USE_SSE_INTRINSICS
const __m128 mult = _mm_load1_ps (&multiplier);
#endif
JUCE_PERFORM_SSE_OP_SRC_DEST (dest[i] += src[i] * multiplier,
_mm_add_ps (d, _mm_mul_ps (mult, s)),
JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST)
}
void JUCE_CALLTYPE FloatVectorOperations::multiply (float* dest, const float* src, int num) noexcept
{
#if JUCE_USE_VDSP_FRAMEWORK
vDSP_vmul (src, 1, dest, 1, dest, 1, num);
#else
JUCE_PERFORM_SSE_OP_SRC_DEST (dest[i] *= src[i],
_mm_mul_ps (d, s),
JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST)
#endif
}
void JUCE_CALLTYPE FloatVectorOperations::multiply (float* dest, float multiplier, int num) noexcept
{
#if JUCE_USE_VDSP_FRAMEWORK
vDSP_vsmul (dest, 1, &multiplier, dest, 1, num);
#else
#if JUCE_USE_SSE_INTRINSICS
const __m128 mult = _mm_load1_ps (&multiplier);
#endif
JUCE_PERFORM_SSE_OP_DEST (dest[i] *= multiplier,
_mm_mul_ps (d, mult),
JUCE_LOAD_DEST)
#endif
}
void JUCE_CALLTYPE FloatVectorOperations::convertFixedToFloat (float* dest, const int* src, float multiplier, int num) noexcept
{
#if JUCE_USE_SSE_INTRINSICS
const __m128 mult = _mm_load1_ps (&multiplier);
#endif
JUCE_PERFORM_SSE_OP_SRC_DEST (dest[i] = src[i] * multiplier,
_mm_mul_ps (mult, _mm_cvtepi32_ps (_mm_loadu_si128 ((const __m128i*) src))),
JUCE_LOAD_NONE, JUCE_INCREMENT_SRC_DEST)
}
void JUCE_CALLTYPE FloatVectorOperations::findMinAndMax (const float* src, int num, float& minResult, float& maxResult) noexcept
{
#if JUCE_USE_SSE_INTRINSICS
const int numLongOps = num / 4;
if (numLongOps > 1 && FloatVectorHelpers::isSSE2Available())
{
__m128 mn, mx;
#define JUCE_MINMAX_SSE_LOOP(loadOp) \
mn = loadOp (src); \
mx = mn; \
src += 4; \
for (int i = 1; i < numLongOps; ++i) \
{ \
const __m128 s = loadOp (src); \
mn = _mm_min_ps (mn, s); \
mx = _mm_max_ps (mx, s); \
src += 4; \
}
if (FloatVectorHelpers::isAligned (src)) { JUCE_MINMAX_SSE_LOOP (_mm_load_ps) }
else { JUCE_MINMAX_SSE_LOOP (_mm_loadu_ps) }
float localMin, localMax;
{
float mns[4], mxs[4];
_mm_storeu_ps (mns, mn);
_mm_storeu_ps (mxs, mx);
FloatVectorHelpers::mmEmpty();
localMin = jmin (mns[0], mns[1], mns[2], mns[3]);
localMax = jmax (mxs[0], mxs[1], mxs[2], mxs[3]);
}
num &= 3;
for (int i = 0; i < num; ++i)
{
const float s = src[i];
localMin = jmin (localMin, s);
localMax = jmax (localMax, s);
}
minResult = localMin;
maxResult = localMax;
return;
}
#endif
juce::findMinAndMax (src, num, minResult, maxResult);
}
float JUCE_CALLTYPE FloatVectorOperations::findMinimum (const float* src, int num) noexcept
{
#if JUCE_USE_SSE_INTRINSICS
return FloatVectorHelpers::findMinimumOrMaximum (src, num, true);
#else
return juce::findMinimum (src, num);
#endif
}
float JUCE_CALLTYPE FloatVectorOperations::findMaximum (const float* src, int num) noexcept
{
#if JUCE_USE_SSE_INTRINSICS
return FloatVectorHelpers::findMinimumOrMaximum (src, num, false);
#else
return juce::findMaximum (src, num);
#endif
}

+ 79
- 0
source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h View File

@@ -0,0 +1,79 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_FLOATVECTOROPERATIONS_H_INCLUDED
#define JUCE_FLOATVECTOROPERATIONS_H_INCLUDED
//==============================================================================
/**
A collection of simple vector operations on arrays of floats, accelerated with
SIMD instructions where possible.
*/
class JUCE_API FloatVectorOperations
{
public:
//==============================================================================
/** Clears a vector of floats. */
static void JUCE_CALLTYPE clear (float* dest, int numValues) noexcept;
/** Copies a repeated value into a vector of floats. */
static void JUCE_CALLTYPE fill (float* dest, float valueToFill, int numValues) noexcept;
/** Copies a vector of floats. */
static void JUCE_CALLTYPE copy (float* dest, const float* src, int numValues) noexcept;
/** Copies a vector of floats, multiplying each value by a given multiplier */
static void JUCE_CALLTYPE copyWithMultiply (float* dest, const float* src, float multiplier, int numValues) noexcept;
/** Adds the source values to the destination values. */
static void JUCE_CALLTYPE add (float* dest, const float* src, int numValues) noexcept;
/** Adds a fixed value to the destination values. */
static void JUCE_CALLTYPE add (float* dest, float amount, int numValues) noexcept;
/** Multiplies each source value by the given multiplier, then adds it to the destination value. */
static void JUCE_CALLTYPE addWithMultiply (float* dest, const float* src, float multiplier, int numValues) noexcept;
/** Multiplies the destination values by the source values. */
static void JUCE_CALLTYPE multiply (float* dest, const float* src, int numValues) noexcept;
/** Multiplies each of the destination values by a fixed multiplier. */
static void JUCE_CALLTYPE multiply (float* dest, float multiplier, int numValues) noexcept;
/** Converts a stream of integers to floats, multiplying each one by the given multiplier. */
static void JUCE_CALLTYPE convertFixedToFloat (float* dest, const int* src, float multiplier, int numValues) noexcept;
/** Finds the miniumum and maximum values in the given array. */
static void JUCE_CALLTYPE findMinAndMax (const float* src, int numValues, float& minResult, float& maxResult) noexcept;
/** Finds the miniumum value in the given array. */
static float JUCE_CALLTYPE findMinimum (const float* src, int numValues) noexcept;
/** Finds the maximum value in the given array. */
static float JUCE_CALLTYPE findMaximum (const float* src, int numValues) noexcept;
};
#endif // JUCE_FLOATVECTOROPERATIONS_H_INCLUDED

+ 103
- 0
source/modules/juce_audio_basics/effects/juce_Decibels.h View File

@@ -0,0 +1,103 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_DECIBELS_H_INCLUDED
#define JUCE_DECIBELS_H_INCLUDED
//==============================================================================
/**
This class contains some helpful static methods for dealing with decibel values.
*/
class Decibels
{
public:
//==============================================================================
/** Converts a dBFS value to its equivalent gain level.
A gain of 1.0 = 0 dB, and lower gains map onto negative decibel values. Any
decibel value lower than minusInfinityDb will return a gain of 0.
*/
template <typename Type>
static Type decibelsToGain (const Type decibels,
const Type minusInfinityDb = (Type) defaultMinusInfinitydB)
{
return decibels > minusInfinityDb ? powf ((Type) 10.0, decibels * (Type) 0.05)
: Type();
}
/** Converts a gain level into a dBFS value.
A gain of 1.0 = 0 dB, and lower gains map onto negative decibel values.
If the gain is 0 (or negative), then the method will return the value
provided as minusInfinityDb.
*/
template <typename Type>
static Type gainToDecibels (const Type gain,
const Type minusInfinityDb = (Type) defaultMinusInfinitydB)
{
return gain > Type() ? jmax (minusInfinityDb, (Type) std::log10 (gain) * (Type) 20.0)
: minusInfinityDb;
}
//==============================================================================
/** Converts a decibel reading to a string, with the 'dB' suffix.
If the decibel value is lower than minusInfinityDb, the return value will
be "-INF dB".
*/
template <typename Type>
static String toString (const Type decibels,
const int decimalPlaces = 2,
const Type minusInfinityDb = (Type) defaultMinusInfinitydB)
{
String s;
if (decibels <= minusInfinityDb)
{
s = "-INF dB";
}
else
{
if (decibels >= Type())
s << '+';
s << String (decibels, decimalPlaces) << " dB";
}
return s;
}
private:
//==============================================================================
enum
{
defaultMinusInfinitydB = -100
};
Decibels(); // This class can't be instantiated, it's just a holder for static methods..
JUCE_DECLARE_NON_COPYABLE (Decibels)
};
#endif // JUCE_DECIBELS_H_INCLUDED

+ 245
- 0
source/modules/juce_audio_basics/effects/juce_IIRFilter.cpp View File

@@ -0,0 +1,245 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#if JUCE_INTEL
#define JUCE_SNAP_TO_ZERO(n) if (! (n < -1.0e-8 || n > 1.0e-8)) n = 0;
#else
#define JUCE_SNAP_TO_ZERO(n)
#endif
//==============================================================================
IIRCoefficients::IIRCoefficients() noexcept
{
zeromem (c, sizeof (c));
}
IIRCoefficients::~IIRCoefficients() noexcept {}
IIRCoefficients::IIRCoefficients (const IIRCoefficients& other) noexcept
{
memcpy (c, other.c, sizeof (c));
}
IIRCoefficients& IIRCoefficients::operator= (const IIRCoefficients& other) noexcept
{
memcpy (c, other.c, sizeof (c));
return *this;
}
IIRCoefficients::IIRCoefficients (double c1, double c2, double c3,
double c4, double c5, double c6) noexcept
{
const double a = 1.0 / c4;
c[0] = (float) (c1 * a);
c[1] = (float) (c2 * a);
c[2] = (float) (c3 * a);
c[3] = (float) (c5 * a);
c[4] = (float) (c6 * a);
}
IIRCoefficients IIRCoefficients::makeLowPass (const double sampleRate,
const double frequency) noexcept
{
jassert (sampleRate > 0);
const double n = 1.0 / tan (double_Pi * frequency / sampleRate);
const double nSquared = n * n;
const double c1 = 1.0 / (1.0 + std::sqrt (2.0) * n + nSquared);
return IIRCoefficients (c1,
c1 * 2.0f,
c1,
1.0,
c1 * 2.0 * (1.0 - nSquared),
c1 * (1.0 - std::sqrt (2.0) * n + nSquared));
}
IIRCoefficients IIRCoefficients::makeHighPass (const double sampleRate,
const double frequency) noexcept
{
const double n = tan (double_Pi * frequency / sampleRate);
const double nSquared = n * n;
const double c1 = 1.0 / (1.0 + std::sqrt (2.0) * n + nSquared);
return IIRCoefficients (c1,
c1 * -2.0f,
c1,
1.0,
c1 * 2.0 * (nSquared - 1.0),
c1 * (1.0 - std::sqrt (2.0) * n + nSquared));
}
IIRCoefficients IIRCoefficients::makeLowShelf (const double sampleRate,
const double cutOffFrequency,
const double Q,
const float gainFactor) noexcept
{
jassert (sampleRate > 0);
jassert (Q > 0);
const double A = jmax (0.0f, std::sqrt (gainFactor));
const double aminus1 = A - 1.0;
const double aplus1 = A + 1.0;
const double omega = (double_Pi * 2.0 * jmax (cutOffFrequency, 2.0)) / sampleRate;
const double coso = std::cos (omega);
const double beta = std::sin (omega) * std::sqrt (A) / Q;
const double aminus1TimesCoso = aminus1 * coso;
return IIRCoefficients (A * (aplus1 - aminus1TimesCoso + beta),
A * 2.0 * (aminus1 - aplus1 * coso),
A * (aplus1 - aminus1TimesCoso - beta),
aplus1 + aminus1TimesCoso + beta,
-2.0 * (aminus1 + aplus1 * coso),
aplus1 + aminus1TimesCoso - beta);
}
IIRCoefficients IIRCoefficients::makeHighShelf (const double sampleRate,
const double cutOffFrequency,
const double Q,
const float gainFactor) noexcept
{
jassert (sampleRate > 0);
jassert (Q > 0);
const double A = jmax (0.0f, std::sqrt (gainFactor));
const double aminus1 = A - 1.0;
const double aplus1 = A + 1.0;
const double omega = (double_Pi * 2.0 * jmax (cutOffFrequency, 2.0)) / sampleRate;
const double coso = std::cos (omega);
const double beta = std::sin (omega) * std::sqrt (A) / Q;
const double aminus1TimesCoso = aminus1 * coso;
return IIRCoefficients (A * (aplus1 + aminus1TimesCoso + beta),
A * -2.0 * (aminus1 + aplus1 * coso),
A * (aplus1 + aminus1TimesCoso - beta),
aplus1 - aminus1TimesCoso + beta,
2.0 * (aminus1 - aplus1 * coso),
aplus1 - aminus1TimesCoso - beta);
}
IIRCoefficients IIRCoefficients::makePeakFilter (const double sampleRate,
const double centreFrequency,
const double Q,
const float gainFactor) noexcept
{
jassert (sampleRate > 0);
jassert (Q > 0);
const double A = jmax (0.0f, std::sqrt (gainFactor));
const double omega = (double_Pi * 2.0 * jmax (centreFrequency, 2.0)) / sampleRate;
const double alpha = 0.5 * std::sin (omega) / Q;
const double c2 = -2.0 * std::cos (omega);
const double alphaTimesA = alpha * A;
const double alphaOverA = alpha / A;
return IIRCoefficients (1.0 + alphaTimesA,
c2,
1.0 - alphaTimesA,
1.0 + alphaOverA,
c2,
1.0 - alphaOverA);
}
//==============================================================================
IIRFilter::IIRFilter() noexcept
: v1 (0), v2 (0), active (false)
{
}
IIRFilter::IIRFilter (const IIRFilter& other) noexcept
: v1 (0), v2 (0), active (other.active)
{
const SpinLock::ScopedLockType sl (other.processLock);
coefficients = other.coefficients;
}
IIRFilter::~IIRFilter() noexcept
{
}
//==============================================================================
void IIRFilter::makeInactive() noexcept
{
const SpinLock::ScopedLockType sl (processLock);
active = false;
}
void IIRFilter::setCoefficients (const IIRCoefficients& newCoefficients) noexcept
{
const SpinLock::ScopedLockType sl (processLock);
coefficients = newCoefficients;
active = true;
}
//==============================================================================
void IIRFilter::reset() noexcept
{
const SpinLock::ScopedLockType sl (processLock);
v1 = v2 = 0;
}
float IIRFilter::processSingleSampleRaw (const float in) noexcept
{
float out = coefficients.c[0] * in + v1;
JUCE_SNAP_TO_ZERO (out);
v1 = coefficients.c[1] * in - coefficients.c[3] * out + v2;
v2 = coefficients.c[2] * in - coefficients.c[4] * out;
return out;
}
void IIRFilter::processSamples (float* const samples,
const int numSamples) noexcept
{
const SpinLock::ScopedLockType sl (processLock);
if (active)
{
const float c0 = coefficients.c[0];
const float c1 = coefficients.c[1];
const float c2 = coefficients.c[2];
const float c3 = coefficients.c[3];
const float c4 = coefficients.c[4];
float lv1 = v1, lv2 = v2;
for (int i = 0; i < numSamples; ++i)
{
const float in = samples[i];
const float out = c0 * in + lv1;
samples[i] = out;
lv1 = c1 * in - c3 * out + lv2;
lv2 = c2 * in - c4 * out;
}
JUCE_SNAP_TO_ZERO (lv1); v1 = lv1;
JUCE_SNAP_TO_ZERO (lv2); v2 = lv2;
}
}
#undef JUCE_SNAP_TO_ZERO

+ 172
- 0
source/modules/juce_audio_basics/effects/juce_IIRFilter.h View File

@@ -0,0 +1,172 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_IIRFILTER_H_INCLUDED
#define JUCE_IIRFILTER_H_INCLUDED
class IIRFilter;
//==============================================================================
/**
A set of coefficients for use in an IIRFilter object.
@see IIRFilter
*/
class JUCE_API IIRCoefficients
{
public:
//==============================================================================
/** Creates a null set of coefficients (which will produce silence). */
IIRCoefficients() noexcept;
/** Directly constructs an object from the raw coefficients.
Most people will want to use the static methods instead of this, but
the constructor is public to allow tinkerers to create their own custom
filters!
*/
IIRCoefficients (double c1, double c2, double c3,
double c4, double c5, double c6) noexcept;
/** Creates a copy of another filter. */
IIRCoefficients (const IIRCoefficients&) noexcept;
/** Creates a copy of another filter. */
IIRCoefficients& operator= (const IIRCoefficients&) noexcept;
/** Destructor. */
~IIRCoefficients() noexcept;
/** Returns the coefficients for a low-pass filter. */
static IIRCoefficients makeLowPass (double sampleRate,
double frequency) noexcept;
/** Returns the coefficients for a high-pass filter. */
static IIRCoefficients makeHighPass (double sampleRate,
double frequency) noexcept;
//==============================================================================
/** Returns the coefficients for a low-pass shelf filter with variable Q and gain.
The gain is a scale factor that the low frequencies are multiplied by, so values
greater than 1.0 will boost the low frequencies, values less than 1.0 will
attenuate them.
*/
static IIRCoefficients makeLowShelf (double sampleRate,
double cutOffFrequency,
double Q,
float gainFactor) noexcept;
/** Returns the coefficients for a high-pass shelf filter with variable Q and gain.
The gain is a scale factor that the high frequencies are multiplied by, so values
greater than 1.0 will boost the high frequencies, values less than 1.0 will
attenuate them.
*/
static IIRCoefficients makeHighShelf (double sampleRate,
double cutOffFrequency,
double Q,
float gainFactor) noexcept;
/** Returns the coefficients for a peak filter centred around a
given frequency, with a variable Q and gain.
The gain is a scale factor that the centre frequencies are multiplied by, so
values greater than 1.0 will boost the centre frequencies, values less than
1.0 will attenuate them.
*/
static IIRCoefficients makePeakFilter (double sampleRate,
double centreFrequency,
double Q,
float gainFactor) noexcept;
private:
friend class IIRFilter;
float c[5];
};
//==============================================================================
/**
An IIR filter that can perform low, high, or band-pass filtering on an
audio signal.
@see IIRCoefficient, IIRFilterAudioSource
*/
class JUCE_API IIRFilter
{
public:
//==============================================================================
/** Creates a filter.
Initially the filter is inactive, so will have no effect on samples that
you process with it. Use the setCoefficients() method to turn it into the
type of filter needed.
*/
IIRFilter() noexcept;
/** Creates a copy of another filter. */
IIRFilter (const IIRFilter&) noexcept;
/** Destructor. */
~IIRFilter() noexcept;
//==============================================================================
/** Clears the filter so that any incoming data passes through unchanged. */
void makeInactive() noexcept;
/** Applies a set of coefficients to this filter. */
void setCoefficients (const IIRCoefficients& newCoefficients) noexcept;
/** Returns the coefficients that this filter is using. */
IIRCoefficients getCoefficients() const noexcept { return coefficients; }
//==============================================================================
/** Resets the filter's processing pipeline, ready to start a new stream of data.
Note that this clears the processing state, but the type of filter and
its coefficients aren't changed. To put a filter into an inactive state, use
the makeInactive() method.
*/
void reset() noexcept;
/** Performs the filter operation on the given set of samples. */
void processSamples (float* samples, int numSamples) noexcept;
/** Processes a single sample, without any locking or checking.
Use this if you need fast processing of a single value, but be aware that
this isn't thread-safe in the way that processSamples() is.
*/
float processSingleSampleRaw (float sample) noexcept;
protected:
//==============================================================================
SpinLock processLock;
IIRCoefficients coefficients;
float v1, v2;
bool active;
IIRFilter& operator= (const IIRFilter&);
JUCE_LEAK_DETECTOR (IIRFilter)
};
#endif // JUCE_IIRFILTER_H_INCLUDED

+ 200
- 0
source/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.cpp View File

@@ -0,0 +1,200 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
namespace LagrangeHelpers
{
template <int k>
struct ResampleHelper
{
static forcedinline void calc (float& a, float b) { a *= b * (1.0f / k); }
};
template<>
struct ResampleHelper <0>
{
static forcedinline void calc (float&, float) {}
};
template <int k>
static forcedinline float calcCoefficient (float input, const float offset) noexcept
{
ResampleHelper <0 - k>::calc (input, -2.0f - offset);
ResampleHelper <1 - k>::calc (input, -1.0f - offset);
ResampleHelper <2 - k>::calc (input, 0.0f - offset);
ResampleHelper <3 - k>::calc (input, 1.0f - offset);
ResampleHelper <4 - k>::calc (input, 2.0f - offset);
return input;
}
static forcedinline float valueAtOffset (const float* const inputs, const float offset) noexcept
{
return calcCoefficient<0> (inputs[4], offset)
+ calcCoefficient<1> (inputs[3], offset)
+ calcCoefficient<2> (inputs[2], offset)
+ calcCoefficient<3> (inputs[1], offset)
+ calcCoefficient<4> (inputs[0], offset);
}
static forcedinline void push (float* inputs, const float newValue) noexcept
{
inputs[4] = inputs[3];
inputs[3] = inputs[2];
inputs[2] = inputs[1];
inputs[1] = inputs[0];
inputs[0] = newValue;
}
}
//==============================================================================
LagrangeInterpolator::LagrangeInterpolator() { reset(); }
LagrangeInterpolator::~LagrangeInterpolator() {}
void LagrangeInterpolator::reset() noexcept
{
subSamplePos = 1.0;
for (int i = 0; i < numElementsInArray (lastInputSamples); ++i)
lastInputSamples[i] = 0;
}
int LagrangeInterpolator::process (const double actualRatio, const float* in,
float* out, const int numOut) noexcept
{
if (actualRatio == 1.0)
{
memcpy (out, in, numOut * sizeof (float));
if (numOut >= 4)
{
memcpy (lastInputSamples, in + (numOut - 4), 4 * sizeof (float));
}
else
{
for (int i = 0; i < numOut; ++i)
LagrangeHelpers::push (lastInputSamples, in[i]);
}
return numOut;
}
const float* const originalIn = in;
double pos = subSamplePos;
if (actualRatio < 1.0)
{
for (int i = numOut; --i >= 0;)
{
if (pos >= 1.0)
{
LagrangeHelpers::push (lastInputSamples, *in++);
pos -= 1.0;
}
*out++ = LagrangeHelpers::valueAtOffset (lastInputSamples, (float) pos);
pos += actualRatio;
}
}
else
{
for (int i = numOut; --i >= 0;)
{
while (pos < actualRatio)
{
LagrangeHelpers::push (lastInputSamples, *in++);
pos += 1.0;
}
pos -= actualRatio;
*out++ = LagrangeHelpers::valueAtOffset (lastInputSamples, 1.0f - (float) pos);
}
}
subSamplePos = pos;
return (int) (in - originalIn);
}
int LagrangeInterpolator::processAdding (const double actualRatio, const float* in,
float* out, const int numOut, const float gain) noexcept
{
if (actualRatio == 1.0)
{
if (gain != 1.0f)
{
for (int i = 0; i < numOut; ++i)
out[i] += in[i] * gain;
}
else
{
for (int i = 0; i < numOut; ++i)
out[i] += in[i];
}
if (numOut >= 4)
{
memcpy (lastInputSamples, in + (numOut - 4), 4 * sizeof (float));
}
else
{
for (int i = 0; i < numOut; ++i)
LagrangeHelpers::push (lastInputSamples, in[i]);
}
return numOut;
}
const float* const originalIn = in;
double pos = subSamplePos;
if (actualRatio < 1.0)
{
for (int i = numOut; --i >= 0;)
{
if (pos >= 1.0)
{
LagrangeHelpers::push (lastInputSamples, *in++);
pos -= 1.0;
}
*out++ += gain * LagrangeHelpers::valueAtOffset (lastInputSamples, (float) pos);
pos += actualRatio;
}
}
else
{
for (int i = numOut; --i >= 0;)
{
while (pos < actualRatio)
{
LagrangeHelpers::push (lastInputSamples, *in++);
pos += 1.0;
}
pos -= actualRatio;
*out++ += gain * LagrangeHelpers::valueAtOffset (lastInputSamples, jmax (0.0f, 1.0f - (float) pos));
}
}
subSamplePos = pos;
return (int) (in - originalIn);
}

+ 93
- 0
source/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.h View File

@@ -0,0 +1,93 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_LAGRANGEINTERPOLATOR_H_INCLUDED
#define JUCE_LAGRANGEINTERPOLATOR_H_INCLUDED
//==============================================================================
/**
Interpolator for resampling a stream of floats using 4-point lagrange interpolation.
Note that the resampler is stateful, so when there's a break in the continuity
of the input stream you're feeding it, you should call reset() before feeding
it any new data. And like with any other stateful filter, if you're resampling
multiple channels, make sure each one uses its own LagrangeInterpolator
object.
*/
class JUCE_API LagrangeInterpolator
{
public:
LagrangeInterpolator();
~LagrangeInterpolator();
/** Resets the state of the interpolator.
Call this when there's a break in the continuity of the input data stream.
*/
void reset() noexcept;
/** Resamples a stream of samples.
@param speedRatio the number of input samples to use for each output sample
@param inputSamples the source data to read from. This must contain at
least (speedRatio * numOutputSamplesToProduce) samples.
@param outputSamples the buffer to write the results into
@param numOutputSamplesToProduce the number of output samples that should be created
@returns the actual number of input samples that were used
*/
int process (double speedRatio,
const float* inputSamples,
float* outputSamples,
int numOutputSamplesToProduce) noexcept;
/** Resamples a stream of samples, adding the results to the output data
with a gain.
@param speedRatio the number of input samples to use for each output sample
@param inputSamples the source data to read from. This must contain at
least (speedRatio * numOutputSamplesToProduce) samples.
@param outputSamples the buffer to write the results to - the result values will be added
to any pre-existing data in this buffer after being multiplied by
the gain factor
@param numOutputSamplesToProduce the number of output samples that should be created
@param gain a gain factor to multiply the resulting samples by before
adding them to the destination buffer
@returns the actual number of input samples that were used
*/
int processAdding (double speedRatio,
const float* inputSamples,
float* outputSamples,
int numOutputSamplesToProduce,
float gain) noexcept;
private:
float lastInputSamples[5];
double subSamplePos;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LagrangeInterpolator)
};
#endif // JUCE_LAGRANGEINTERPOLATOR_H_INCLUDED

+ 323
- 0
source/modules/juce_audio_basics/effects/juce_Reverb.h View File

@@ -0,0 +1,323 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_REVERB_H_INCLUDED
#define JUCE_REVERB_H_INCLUDED
//==============================================================================
/**
Performs a simple reverb effect on a stream of audio data.
This is a simple stereo reverb, based on the technique and tunings used in FreeVerb.
Use setSampleRate() to prepare it, and then call processStereo() or processMono() to
apply the reverb to your audio data.
@see ReverbAudioSource
*/
class Reverb
{
public:
//==============================================================================
Reverb()
{
setParameters (Parameters());
setSampleRate (44100.0);
}
//==============================================================================
/** Holds the parameters being used by a Reverb object. */
struct Parameters
{
Parameters() noexcept
: roomSize (0.5f),
damping (0.5f),
wetLevel (0.33f),
dryLevel (0.4f),
width (1.0f),
freezeMode (0)
{}
float roomSize; /**< Room size, 0 to 1.0, where 1.0 is big, 0 is small. */
float damping; /**< Damping, 0 to 1.0, where 0 is not damped, 1.0 is fully damped. */
float wetLevel; /**< Wet level, 0 to 1.0 */
float dryLevel; /**< Dry level, 0 to 1.0 */
float width; /**< Reverb width, 0 to 1.0, where 1.0 is very wide. */
float freezeMode; /**< Freeze mode - values < 0.5 are "normal" mode, values > 0.5
put the reverb into a continuous feedback loop. */
};
//==============================================================================
/** Returns the reverb's current parameters. */
const Parameters& getParameters() const noexcept { return parameters; }
/** Applies a new set of parameters to the reverb.
Note that this doesn't attempt to lock the reverb, so if you call this in parallel with
the process method, you may get artifacts.
*/
void setParameters (const Parameters& newParams)
{
const float wetScaleFactor = 3.0f;
const float dryScaleFactor = 2.0f;
const float wet = newParams.wetLevel * wetScaleFactor;
wet1 = wet * (newParams.width * 0.5f + 0.5f);
wet2 = wet * (1.0f - newParams.width) * 0.5f;
dry = newParams.dryLevel * dryScaleFactor;
gain = isFrozen (newParams.freezeMode) ? 0.0f : 0.015f;
parameters = newParams;
shouldUpdateDamping = true;
}
//==============================================================================
/** Sets the sample rate that will be used for the reverb.
You must call this before the process methods, in order to tell it the correct sample rate.
*/
void setSampleRate (const double sampleRate)
{
jassert (sampleRate > 0);
static const short combTunings[] = { 1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617 }; // (at 44100Hz)
static const short allPassTunings[] = { 556, 441, 341, 225 };
const int stereoSpread = 23;
const int intSampleRate = (int) sampleRate;
for (int i = 0; i < numCombs; ++i)
{
comb[0][i].setSize ((intSampleRate * combTunings[i]) / 44100);
comb[1][i].setSize ((intSampleRate * (combTunings[i] + stereoSpread)) / 44100);
}
for (int i = 0; i < numAllPasses; ++i)
{
allPass[0][i].setSize ((intSampleRate * allPassTunings[i]) / 44100);
allPass[1][i].setSize ((intSampleRate * (allPassTunings[i] + stereoSpread)) / 44100);
}
shouldUpdateDamping = true;
}
/** Clears the reverb's buffers. */
void reset()
{
for (int j = 0; j < numChannels; ++j)
{
for (int i = 0; i < numCombs; ++i)
comb[j][i].clear();
for (int i = 0; i < numAllPasses; ++i)
allPass[j][i].clear();
}
}
//==============================================================================
/** Applies the reverb to two stereo channels of audio data. */
void processStereo (float* const left, float* const right, const int numSamples) noexcept
{
jassert (left != nullptr && right != nullptr);
if (shouldUpdateDamping)
updateDamping();
for (int i = 0; i < numSamples; ++i)
{
const float input = (left[i] + right[i]) * gain;
float outL = 0, outR = 0;
for (int j = 0; j < numCombs; ++j) // accumulate the comb filters in parallel
{
outL += comb[0][j].process (input);
outR += comb[1][j].process (input);
}
for (int j = 0; j < numAllPasses; ++j) // run the allpass filters in series
{
outL = allPass[0][j].process (outL);
outR = allPass[1][j].process (outR);
}
left[i] = outL * wet1 + outR * wet2 + left[i] * dry;
right[i] = outR * wet1 + outL * wet2 + right[i] * dry;
}
}
/** Applies the reverb to a single mono channel of audio data. */
void processMono (float* const samples, const int numSamples) noexcept
{
jassert (samples != nullptr);
if (shouldUpdateDamping)
updateDamping();
for (int i = 0; i < numSamples; ++i)
{
const float input = samples[i] * gain;
float output = 0;
for (int j = 0; j < numCombs; ++j) // accumulate the comb filters in parallel
output += comb[0][j].process (input);
for (int j = 0; j < numAllPasses; ++j) // run the allpass filters in series
output = allPass[0][j].process (output);
samples[i] = output * wet1 + input * dry;
}
}
private:
//==============================================================================
Parameters parameters;
volatile bool shouldUpdateDamping;
float gain, wet1, wet2, dry;
inline static bool isFrozen (const float freezeMode) noexcept { return freezeMode >= 0.5f; }
void updateDamping() noexcept
{
const float roomScaleFactor = 0.28f;
const float roomOffset = 0.7f;
const float dampScaleFactor = 0.4f;
shouldUpdateDamping = false;
if (isFrozen (parameters.freezeMode))
setDamping (0.0f, 1.0f);
else
setDamping (parameters.damping * dampScaleFactor,
parameters.roomSize * roomScaleFactor + roomOffset);
}
void setDamping (const float dampingToUse, const float roomSizeToUse) noexcept
{
for (int j = 0; j < numChannels; ++j)
for (int i = numCombs; --i >= 0;)
comb[j][i].setFeedbackAndDamp (roomSizeToUse, dampingToUse);
}
//==============================================================================
class CombFilter
{
public:
CombFilter() noexcept
: bufferSize (0), bufferIndex (0),
feedback (0), last (0), damp1 (0), damp2 (0)
{}
void setSize (const int size)
{
if (size != bufferSize)
{
bufferIndex = 0;
buffer.malloc ((size_t) size);
bufferSize = size;
}
clear();
}
void clear() noexcept
{
last = 0;
buffer.clear ((size_t) bufferSize);
}
void setFeedbackAndDamp (const float f, const float d) noexcept
{
damp1 = d;
damp2 = 1.0f - d;
feedback = f;
}
inline float process (const float input) noexcept
{
const float output = buffer [bufferIndex];
last = (output * damp2) + (last * damp1);
JUCE_UNDENORMALISE (last);
float temp = input + (last * feedback);
JUCE_UNDENORMALISE (temp);
buffer [bufferIndex] = temp;
bufferIndex = (bufferIndex + 1) % bufferSize;
return output;
}
private:
HeapBlock<float> buffer;
int bufferSize, bufferIndex;
float feedback, last, damp1, damp2;
JUCE_DECLARE_NON_COPYABLE (CombFilter)
};
//==============================================================================
class AllPassFilter
{
public:
AllPassFilter() noexcept : bufferSize (0), bufferIndex (0) {}
void setSize (const int size)
{
if (size != bufferSize)
{
bufferIndex = 0;
buffer.malloc ((size_t) size);
bufferSize = size;
}
clear();
}
void clear() noexcept
{
buffer.clear ((size_t) bufferSize);
}
inline float process (const float input) noexcept
{
const float bufferedValue = buffer [bufferIndex];
float temp = input + (bufferedValue * 0.5f);
JUCE_UNDENORMALISE (temp);
buffer [bufferIndex] = temp;
bufferIndex = (bufferIndex + 1) % bufferSize;
return bufferedValue - input;
}
private:
HeapBlock<float> buffer;
int bufferSize, bufferIndex;
JUCE_DECLARE_NON_COPYABLE (AllPassFilter)
};
enum { numCombs = 8, numAllPasses = 4, numChannels = 2 };
CombFilter comb [numChannels][numCombs];
AllPassFilter allPass [numChannels][numAllPasses];
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Reverb)
};
#endif // JUCE_REVERB_H_INCLUDED

+ 80
- 0
source/modules/juce_audio_basics/juce_audio_basics.cpp View File

@@ -0,0 +1,80 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#if defined (JUCE_AUDIO_BASICS_H_INCLUDED) && ! JUCE_AMALGAMATED_INCLUDE
/* When you add this cpp file to your project, you mustn't include it in a file where you've
already included any other headers - just put it inside a file on its own, possibly with your config
flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix
header files that the compiler may be using.
*/
#error "Incorrect use of JUCE cpp file"
#endif
// Your project must contain an AppConfig.h file with your project-specific settings in it,
// and your header search path must make it accessible to the module's files.
#include "AppConfig.h"
#include "juce_audio_basics.h"
#ifndef JUCE_USE_SSE_INTRINSICS
#define JUCE_USE_SSE_INTRINSICS 1
#endif
#if ! JUCE_INTEL
#undef JUCE_USE_SSE_INTRINSICS
#endif
#if JUCE_USE_SSE_INTRINSICS
#include <emmintrin.h>
#endif
#if JUCE_MAC || JUCE_IOS
#define JUCE_USE_VDSP_FRAMEWORK 1
#include <Accelerate/Accelerate.h>
#endif
namespace juce
{
// START_AUTOINCLUDE buffers/*.cpp, effects/*.cpp, midi/*.cpp, sources/*.cpp, synthesisers/*.cpp
#include "buffers/juce_AudioDataConverters.cpp"
#include "buffers/juce_AudioSampleBuffer.cpp"
#include "buffers/juce_FloatVectorOperations.cpp"
#include "effects/juce_IIRFilter.cpp"
#include "effects/juce_LagrangeInterpolator.cpp"
#include "midi/juce_MidiBuffer.cpp"
#include "midi/juce_MidiFile.cpp"
#include "midi/juce_MidiKeyboardState.cpp"
#include "midi/juce_MidiMessage.cpp"
#include "midi/juce_MidiMessageSequence.cpp"
#include "sources/juce_BufferingAudioSource.cpp"
#include "sources/juce_ChannelRemappingAudioSource.cpp"
#include "sources/juce_IIRFilterAudioSource.cpp"
#include "sources/juce_MixerAudioSource.cpp"
#include "sources/juce_ResamplingAudioSource.cpp"
#include "sources/juce_ReverbAudioSource.cpp"
#include "sources/juce_ToneGeneratorAudioSource.cpp"
#include "synthesisers/juce_Synthesiser.cpp"
// END_AUTOINCLUDE
}

+ 61
- 0
source/modules/juce_audio_basics/juce_audio_basics.h View File

@@ -0,0 +1,61 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_AUDIO_BASICS_H_INCLUDED
#define JUCE_AUDIO_BASICS_H_INCLUDED
#include "../juce_core/juce_core.h"
//=============================================================================
namespace juce
{
// START_AUTOINCLUDE buffers, effects, midi, sources, synthesisers
#include "buffers/juce_AudioDataConverters.h"
#include "buffers/juce_AudioSampleBuffer.h"
#include "buffers/juce_FloatVectorOperations.h"
#include "effects/juce_Decibels.h"
#include "effects/juce_IIRFilter.h"
#include "effects/juce_LagrangeInterpolator.h"
#include "effects/juce_Reverb.h"
#include "midi/juce_MidiBuffer.h"
#include "midi/juce_MidiFile.h"
#include "midi/juce_MidiKeyboardState.h"
#include "midi/juce_MidiMessage.h"
#include "midi/juce_MidiMessageSequence.h"
#include "sources/juce_AudioSource.h"
#include "sources/juce_BufferingAudioSource.h"
#include "sources/juce_ChannelRemappingAudioSource.h"
#include "sources/juce_IIRFilterAudioSource.h"
#include "sources/juce_MixerAudioSource.h"
#include "sources/juce_PositionableAudioSource.h"
#include "sources/juce_ResamplingAudioSource.h"
#include "sources/juce_ReverbAudioSource.h"
#include "sources/juce_ToneGeneratorAudioSource.h"
#include "synthesisers/juce_Synthesiser.h"
// END_AUTOINCLUDE
}
#endif // JUCE_AUDIO_BASICS_H_INCLUDED

+ 25
- 0
source/modules/juce_audio_basics/juce_audio_basics.mm View File

@@ -0,0 +1,25 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#include "juce_audio_basics.cpp"

+ 24
- 0
source/modules/juce_audio_basics/juce_module_info View File

@@ -0,0 +1,24 @@
{
"id": "juce_audio_basics",
"name": "JUCE audio and midi data classes",
"version": "2.1.2",
"description": "Classes for audio buffer manipulation, midi message handling, synthesis, etc",
"website": "http://www.juce.com/juce",
"license": "GPL/Commercial",
"dependencies": [ { "id": "juce_core", "version": "matching" } ],
"include": "juce_audio_basics.h",
"compile": [ { "file": "juce_audio_basics.cpp", "target": "! xcode" },
{ "file": "juce_audio_basics.mm", "target": "xcode" } ],
"browse": [ "buffers/*",
"midi/*",
"effects/*",
"sources/*",
"synthesisers/*" ],
"OSXFrameworks": "Accelerate",
"iOSFrameworks": "Accelerate"
}

+ 289
- 0
source/modules/juce_audio_basics/midi/juce_MidiBuffer.cpp View File

@@ -0,0 +1,289 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
namespace MidiBufferHelpers
{
inline int getEventTime (const void* const d) noexcept
{
return *static_cast <const int*> (d);
}
inline uint16 getEventDataSize (const void* const d) noexcept
{
return *reinterpret_cast <const uint16*> (static_cast <const char*> (d) + sizeof (int));
}
inline uint16 getEventTotalSize (const void* const d) noexcept
{
return getEventDataSize (d) + sizeof (int) + sizeof (uint16);
}
static int findActualEventLength (const uint8* const data, const int maxBytes) noexcept
{
unsigned int byte = (unsigned int) *data;
int size = 0;
if (byte == 0xf0 || byte == 0xf7)
{
const uint8* d = data + 1;
while (d < data + maxBytes)
if (*d++ == 0xf7)
break;
size = (int) (d - data);
}
else if (byte == 0xff)
{
int n;
const int bytesLeft = MidiMessage::readVariableLengthVal (data + 1, n);
size = jmin (maxBytes, n + 2 + bytesLeft);
}
else if (byte >= 0x80)
{
size = jmin (maxBytes, MidiMessage::getMessageLengthFromFirstByte ((uint8) byte));
}
return size;
}
}
//==============================================================================
MidiBuffer::MidiBuffer() noexcept
: bytesUsed (0)
{
}
MidiBuffer::MidiBuffer (const MidiMessage& message) noexcept
: bytesUsed (0)
{
addEvent (message, 0);
}
MidiBuffer::MidiBuffer (const MidiBuffer& other) noexcept
: data (other.data),
bytesUsed (other.bytesUsed)
{
}
MidiBuffer& MidiBuffer::operator= (const MidiBuffer& other) noexcept
{
bytesUsed = other.bytesUsed;
data = other.data;
return *this;
}
void MidiBuffer::swapWith (MidiBuffer& other) noexcept
{
data.swapWith (other.data);
std::swap (bytesUsed, other.bytesUsed);
}
MidiBuffer::~MidiBuffer()
{
}
inline uint8* MidiBuffer::getData() const noexcept
{
return static_cast <uint8*> (data.getData());
}
void MidiBuffer::clear() noexcept
{
bytesUsed = 0;
}
void MidiBuffer::clear (const int startSample, const int numSamples)
{
uint8* const start = findEventAfter (getData(), startSample - 1);
uint8* const end = findEventAfter (start, startSample + numSamples - 1);
if (end > start)
{
const int bytesToMove = bytesUsed - (int) (end - getData());
if (bytesToMove > 0)
memmove (start, end, (size_t) bytesToMove);
bytesUsed -= (int) (end - start);
}
}
void MidiBuffer::addEvent (const MidiMessage& m, const int sampleNumber)
{
addEvent (m.getRawData(), m.getRawDataSize(), sampleNumber);
}
void MidiBuffer::addEvent (const void* const newData, const int maxBytes, const int sampleNumber)
{
const int numBytes = MidiBufferHelpers::findActualEventLength (static_cast <const uint8*> (newData), maxBytes);
if (numBytes > 0)
{
size_t spaceNeeded = (size_t) bytesUsed + (size_t) numBytes + sizeof (int) + sizeof (uint16);
data.ensureSize ((spaceNeeded + spaceNeeded / 2 + 8) & ~(size_t) 7);
uint8* d = findEventAfter (getData(), sampleNumber);
const int bytesToMove = bytesUsed - (int) (d - getData());
if (bytesToMove > 0)
memmove (d + numBytes + sizeof (int) + sizeof (uint16), d, (size_t) bytesToMove);
*reinterpret_cast <int*> (d) = sampleNumber;
d += sizeof (int);
*reinterpret_cast <uint16*> (d) = (uint16) numBytes;
d += sizeof (uint16);
memcpy (d, newData, (size_t) numBytes);
bytesUsed += sizeof (int) + sizeof (uint16) + (size_t) numBytes;
}
}
void MidiBuffer::addEvents (const MidiBuffer& otherBuffer,
const int startSample,
const int numSamples,
const int sampleDeltaToAdd)
{
Iterator i (otherBuffer);
i.setNextSamplePosition (startSample);
const uint8* eventData;
int eventSize, position;
while (i.getNextEvent (eventData, eventSize, position)
&& (position < startSample + numSamples || numSamples < 0))
{
addEvent (eventData, eventSize, position + sampleDeltaToAdd);
}
}
void MidiBuffer::ensureSize (size_t minimumNumBytes)
{
data.ensureSize (minimumNumBytes);
}
bool MidiBuffer::isEmpty() const noexcept
{
return bytesUsed == 0;
}
int MidiBuffer::getNumEvents() const noexcept
{
int n = 0;
const uint8* d = getData();
const uint8* const end = d + bytesUsed;
while (d < end)
{
d += MidiBufferHelpers::getEventTotalSize (d);
++n;
}
return n;
}
int MidiBuffer::getFirstEventTime() const noexcept
{
return bytesUsed > 0 ? MidiBufferHelpers::getEventTime (data.getData()) : 0;
}
int MidiBuffer::getLastEventTime() const noexcept
{
if (bytesUsed == 0)
return 0;
const uint8* d = getData();
const uint8* const endData = d + bytesUsed;
for (;;)
{
const uint8* const nextOne = d + MidiBufferHelpers::getEventTotalSize (d);
if (nextOne >= endData)
return MidiBufferHelpers::getEventTime (d);
d = nextOne;
}
}
uint8* MidiBuffer::findEventAfter (uint8* d, const int samplePosition) const noexcept
{
const uint8* const endData = getData() + bytesUsed;
while (d < endData && MidiBufferHelpers::getEventTime (d) <= samplePosition)
d += MidiBufferHelpers::getEventTotalSize (d);
return d;
}
//==============================================================================
MidiBuffer::Iterator::Iterator (const MidiBuffer& buffer_) noexcept
: buffer (buffer_),
data (buffer_.getData())
{
}
MidiBuffer::Iterator::~Iterator() noexcept
{
}
//==============================================================================
void MidiBuffer::Iterator::setNextSamplePosition (const int samplePosition) noexcept
{
data = buffer.getData();
const uint8* dataEnd = data + buffer.bytesUsed;
while (data < dataEnd && MidiBufferHelpers::getEventTime (data) < samplePosition)
data += MidiBufferHelpers::getEventTotalSize (data);
}
bool MidiBuffer::Iterator::getNextEvent (const uint8* &midiData, int& numBytes, int& samplePosition) noexcept
{
if (data >= buffer.getData() + buffer.bytesUsed)
return false;
samplePosition = MidiBufferHelpers::getEventTime (data);
numBytes = MidiBufferHelpers::getEventDataSize (data);
data += sizeof (int) + sizeof (uint16);
midiData = data;
data += numBytes;
return true;
}
bool MidiBuffer::Iterator::getNextEvent (MidiMessage& result, int& samplePosition) noexcept
{
if (data >= buffer.getData() + buffer.bytesUsed)
return false;
samplePosition = MidiBufferHelpers::getEventTime (data);
const int numBytes = MidiBufferHelpers::getEventDataSize (data);
data += sizeof (int) + sizeof (uint16);
result = MidiMessage (data, numBytes, samplePosition);
data += numBytes;
return true;
}

+ 240
- 0
source/modules/juce_audio_basics/midi/juce_MidiBuffer.h View File

@@ -0,0 +1,240 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_MIDIBUFFER_H_INCLUDED
#define JUCE_MIDIBUFFER_H_INCLUDED
#include "juce_MidiMessage.h"
//==============================================================================
/**
Holds a sequence of time-stamped midi events.
Analogous to the AudioSampleBuffer, this holds a set of midi events with
integer time-stamps. The buffer is kept sorted in order of the time-stamps.
If you're working with a sequence of midi events that may need to be manipulated
or read/written to a midi file, then MidiMessageSequence is probably a more
appropriate container. MidiBuffer is designed for lower-level streams of raw
midi data.
@see MidiMessage
*/
class JUCE_API MidiBuffer
{
public:
//==============================================================================
/** Creates an empty MidiBuffer. */
MidiBuffer() noexcept;
/** Creates a MidiBuffer containing a single midi message. */
explicit MidiBuffer (const MidiMessage& message) noexcept;
/** Creates a copy of another MidiBuffer. */
MidiBuffer (const MidiBuffer& other) noexcept;
/** Makes a copy of another MidiBuffer. */
MidiBuffer& operator= (const MidiBuffer& other) noexcept;
/** Destructor */
~MidiBuffer();
//==============================================================================
/** Removes all events from the buffer. */
void clear() noexcept;
/** Removes all events between two times from the buffer.
All events for which (start <= event position < start + numSamples) will
be removed.
*/
void clear (int start, int numSamples);
/** Returns true if the buffer is empty.
To actually retrieve the events, use a MidiBuffer::Iterator object
*/
bool isEmpty() const noexcept;
/** Counts the number of events in the buffer.
This is actually quite a slow operation, as it has to iterate through all
the events, so you might prefer to call isEmpty() if that's all you need
to know.
*/
int getNumEvents() const noexcept;
/** Adds an event to the buffer.
The sample number will be used to determine the position of the event in
the buffer, which is always kept sorted. The MidiMessage's timestamp is
ignored.
If an event is added whose sample position is the same as one or more events
already in the buffer, the new event will be placed after the existing ones.
To retrieve events, use a MidiBuffer::Iterator object
*/
void addEvent (const MidiMessage& midiMessage, int sampleNumber);
/** Adds an event to the buffer from raw midi data.
The sample number will be used to determine the position of the event in
the buffer, which is always kept sorted.
If an event is added whose sample position is the same as one or more events
already in the buffer, the new event will be placed after the existing ones.
The event data will be inspected to calculate the number of bytes in length that
the midi event really takes up, so maxBytesOfMidiData may be longer than the data
that actually gets stored. E.g. if you pass in a note-on and a length of 4 bytes,
it'll actually only store 3 bytes. If the midi data is invalid, it might not
add an event at all.
To retrieve events, use a MidiBuffer::Iterator object
*/
void addEvent (const void* rawMidiData,
int maxBytesOfMidiData,
int sampleNumber);
/** Adds some events from another buffer to this one.
@param otherBuffer the buffer containing the events you want to add
@param startSample the lowest sample number in the source buffer for which
events should be added. Any source events whose timestamp is
less than this will be ignored
@param numSamples the valid range of samples from the source buffer for which
events should be added - i.e. events in the source buffer whose
timestamp is greater than or equal to (startSample + numSamples)
will be ignored. If this value is less than 0, all events after
startSample will be taken.
@param sampleDeltaToAdd a value which will be added to the source timestamps of the events
that are added to this buffer
*/
void addEvents (const MidiBuffer& otherBuffer,
int startSample,
int numSamples,
int sampleDeltaToAdd);
/** Returns the sample number of the first event in the buffer.
If the buffer's empty, this will just return 0.
*/
int getFirstEventTime() const noexcept;
/** Returns the sample number of the last event in the buffer.
If the buffer's empty, this will just return 0.
*/
int getLastEventTime() const noexcept;
//==============================================================================
/** Exchanges the contents of this buffer with another one.
This is a quick operation, because no memory allocating or copying is done, it
just swaps the internal state of the two buffers.
*/
void swapWith (MidiBuffer& other) noexcept;
/** Preallocates some memory for the buffer to use.
This helps to avoid needing to reallocate space when the buffer has messages
added to it.
*/
void ensureSize (size_t minimumNumBytes);
//==============================================================================
/**
Used to iterate through the events in a MidiBuffer.
Note that altering the buffer while an iterator is using it isn't a
safe operation.
@see MidiBuffer
*/
class JUCE_API Iterator
{
public:
//==============================================================================
/** Creates an Iterator for this MidiBuffer. */
Iterator (const MidiBuffer& buffer) noexcept;
/** Destructor. */
~Iterator() noexcept;
//==============================================================================
/** Repositions the iterator so that the next event retrieved will be the first
one whose sample position is at greater than or equal to the given position.
*/
void setNextSamplePosition (int samplePosition) noexcept;
/** Retrieves a copy of the next event from the buffer.
@param result on return, this will be the message (the MidiMessage's timestamp
is not set)
@param samplePosition on return, this will be the position of the event
@returns true if an event was found, or false if the iterator has reached
the end of the buffer
*/
bool getNextEvent (MidiMessage& result,
int& samplePosition) noexcept;
/** Retrieves the next event from the buffer.
@param midiData on return, this pointer will be set to a block of data containing
the midi message. Note that to make it fast, this is a pointer
directly into the MidiBuffer's internal data, so is only valid
temporarily until the MidiBuffer is altered.
@param numBytesOfMidiData on return, this is the number of bytes of data used by the
midi message
@param samplePosition on return, this will be the position of the event
@returns true if an event was found, or false if the iterator has reached
the end of the buffer
*/
bool getNextEvent (const uint8* &midiData,
int& numBytesOfMidiData,
int& samplePosition) noexcept;
private:
//==============================================================================
const MidiBuffer& buffer;
const uint8* data;
JUCE_DECLARE_NON_COPYABLE (Iterator)
};
private:
//==============================================================================
friend class MidiBuffer::Iterator;
MemoryBlock data;
int bytesUsed;
uint8* getData() const noexcept;
uint8* findEventAfter (uint8*, int samplePosition) const noexcept;
JUCE_LEAK_DETECTOR (MidiBuffer)
};
#endif // JUCE_MIDIBUFFER_H_INCLUDED

+ 424
- 0
source/modules/juce_audio_basics/midi/juce_MidiFile.cpp View File

@@ -0,0 +1,424 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
namespace MidiFileHelpers
{
static void writeVariableLengthInt (OutputStream& out, unsigned int v)
{
unsigned int buffer = v & 0x7f;
while ((v >>= 7) != 0)
{
buffer <<= 8;
buffer |= ((v & 0x7f) | 0x80);
}
for (;;)
{
out.writeByte ((char) buffer);
if (buffer & 0x80)
buffer >>= 8;
else
break;
}
}
static bool parseMidiHeader (const uint8* &data, short& timeFormat, short& fileType, short& numberOfTracks) noexcept
{
unsigned int ch = ByteOrder::bigEndianInt (data);
data += 4;
if (ch != ByteOrder::bigEndianInt ("MThd"))
{
bool ok = false;
if (ch == ByteOrder::bigEndianInt ("RIFF"))
{
for (int i = 0; i < 8; ++i)
{
ch = ByteOrder::bigEndianInt (data);
data += 4;
if (ch == ByteOrder::bigEndianInt ("MThd"))
{
ok = true;
break;
}
}
}
if (! ok)
return false;
}
unsigned int bytesRemaining = ByteOrder::bigEndianInt (data);
data += 4;
fileType = (short) ByteOrder::bigEndianShort (data);
data += 2;
numberOfTracks = (short) ByteOrder::bigEndianShort (data);
data += 2;
timeFormat = (short) ByteOrder::bigEndianShort (data);
data += 2;
bytesRemaining -= 6;
data += bytesRemaining;
return true;
}
static double convertTicksToSeconds (const double time,
const MidiMessageSequence& tempoEvents,
const int timeFormat)
{
if (timeFormat < 0)
return time / (-(timeFormat >> 8) * (timeFormat & 0xff));
double lastTime = 0.0, correctedTime = 0.0;
const double tickLen = 1.0 / (timeFormat & 0x7fff);
double secsPerTick = 0.5 * tickLen;
const int numEvents = tempoEvents.getNumEvents();
for (int i = 0; i < numEvents; ++i)
{
const MidiMessage& m = tempoEvents.getEventPointer(i)->message;
const double eventTime = m.getTimeStamp();
if (eventTime >= time)
break;
correctedTime += (eventTime - lastTime) * secsPerTick;
lastTime = eventTime;
if (m.isTempoMetaEvent())
secsPerTick = tickLen * m.getTempoSecondsPerQuarterNote();
while (i + 1 < numEvents)
{
const MidiMessage& m2 = tempoEvents.getEventPointer(i + 1)->message;
if (m2.getTimeStamp() != eventTime)
break;
if (m2.isTempoMetaEvent())
secsPerTick = tickLen * m2.getTempoSecondsPerQuarterNote();
++i;
}
}
return correctedTime + (time - lastTime) * secsPerTick;
}
// a comparator that puts all the note-offs before note-ons that have the same time
struct Sorter
{
static int compareElements (const MidiMessageSequence::MidiEventHolder* const first,
const MidiMessageSequence::MidiEventHolder* const second) noexcept
{
const double diff = (first->message.getTimeStamp() - second->message.getTimeStamp());
if (diff > 0) return 1;
if (diff < 0) return -1;
if (first->message.isNoteOff() && second->message.isNoteOn()) return -1;
if (first->message.isNoteOn() && second->message.isNoteOff()) return 1;
return 0;
}
};
}
//==============================================================================
MidiFile::MidiFile()
: timeFormat ((short) (unsigned short) 0xe728)
{
}
MidiFile::~MidiFile()
{
}
void MidiFile::clear()
{
tracks.clear();
}
//==============================================================================
int MidiFile::getNumTracks() const noexcept
{
return tracks.size();
}
const MidiMessageSequence* MidiFile::getTrack (const int index) const noexcept
{
return tracks [index];
}
void MidiFile::addTrack (const MidiMessageSequence& trackSequence)
{
tracks.add (new MidiMessageSequence (trackSequence));
}
//==============================================================================
short MidiFile::getTimeFormat() const noexcept
{
return timeFormat;
}
void MidiFile::setTicksPerQuarterNote (const int ticks) noexcept
{
timeFormat = (short) ticks;
}
void MidiFile::setSmpteTimeFormat (const int framesPerSecond,
const int subframeResolution) noexcept
{
timeFormat = (short) (((-framesPerSecond) << 8) | subframeResolution);
}
//==============================================================================
void MidiFile::findAllTempoEvents (MidiMessageSequence& tempoChangeEvents) const
{
for (int i = tracks.size(); --i >= 0;)
{
const int numEvents = tracks.getUnchecked(i)->getNumEvents();
for (int j = 0; j < numEvents; ++j)
{
const MidiMessage& m = tracks.getUnchecked(i)->getEventPointer (j)->message;
if (m.isTempoMetaEvent())
tempoChangeEvents.addEvent (m);
}
}
}
void MidiFile::findAllTimeSigEvents (MidiMessageSequence& timeSigEvents) const
{
for (int i = tracks.size(); --i >= 0;)
{
const int numEvents = tracks.getUnchecked(i)->getNumEvents();
for (int j = 0; j < numEvents; ++j)
{
const MidiMessage& m = tracks.getUnchecked(i)->getEventPointer (j)->message;
if (m.isTimeSignatureMetaEvent())
timeSigEvents.addEvent (m);
}
}
}
double MidiFile::getLastTimestamp() const
{
double t = 0.0;
for (int i = tracks.size(); --i >= 0;)
t = jmax (t, tracks.getUnchecked(i)->getEndTime());
return t;
}
//==============================================================================
bool MidiFile::readFrom (InputStream& sourceStream)
{
clear();
MemoryBlock data;
const int maxSensibleMidiFileSize = 2 * 1024 * 1024;
// (put a sanity-check on the file size, as midi files are generally small)
if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize))
{
size_t size = data.getSize();
const uint8* d = static_cast <const uint8*> (data.getData());
short fileType, expectedTracks;
if (size > 16 && MidiFileHelpers::parseMidiHeader (d, timeFormat, fileType, expectedTracks))
{
size -= (size_t) (d - static_cast <const uint8*> (data.getData()));
int track = 0;
while (size > 0 && track < expectedTracks)
{
const int chunkType = (int) ByteOrder::bigEndianInt (d);
d += 4;
const int chunkSize = (int) ByteOrder::bigEndianInt (d);
d += 4;
if (chunkSize <= 0)
break;
if (chunkType == (int) ByteOrder::bigEndianInt ("MTrk"))
readNextTrack (d, chunkSize);
size -= (size_t) chunkSize + 8;
d += chunkSize;
++track;
}
return true;
}
}
return false;
}
void MidiFile::readNextTrack (const uint8* data, int size)
{
double time = 0;
uint8 lastStatusByte = 0;
MidiMessageSequence result;
while (size > 0)
{
int bytesUsed;
const int delay = MidiMessage::readVariableLengthVal (data, bytesUsed);
data += bytesUsed;
size -= bytesUsed;
time += delay;
int messSize = 0;
const MidiMessage mm (data, size, messSize, lastStatusByte, time);
if (messSize <= 0)
break;
size -= messSize;
data += messSize;
result.addEvent (mm);
const uint8 firstByte = *(mm.getRawData());
if ((firstByte & 0xf0) != 0xf0)
lastStatusByte = firstByte;
}
// use a sort that puts all the note-offs before note-ons that have the same time
MidiFileHelpers::Sorter sorter;
result.list.sort (sorter, true);
addTrack (result);
tracks.getLast()->updateMatchedPairs();
}
//==============================================================================
void MidiFile::convertTimestampTicksToSeconds()
{
MidiMessageSequence tempoEvents;
findAllTempoEvents (tempoEvents);
findAllTimeSigEvents (tempoEvents);
if (timeFormat != 0)
{
for (int i = 0; i < tracks.size(); ++i)
{
const MidiMessageSequence& ms = *tracks.getUnchecked(i);
for (int j = ms.getNumEvents(); --j >= 0;)
{
MidiMessage& m = ms.getEventPointer(j)->message;
m.setTimeStamp (MidiFileHelpers::convertTicksToSeconds (m.getTimeStamp(),
tempoEvents,
timeFormat));
}
}
}
}
//==============================================================================
bool MidiFile::writeTo (OutputStream& out)
{
out.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MThd"));
out.writeIntBigEndian (6);
out.writeShortBigEndian (1); // type
out.writeShortBigEndian ((short) tracks.size());
out.writeShortBigEndian (timeFormat);
for (int i = 0; i < tracks.size(); ++i)
writeTrack (out, i);
out.flush();
return true;
}
void MidiFile::writeTrack (OutputStream& mainOut, const int trackNum)
{
MemoryOutputStream out;
const MidiMessageSequence& ms = *tracks.getUnchecked (trackNum);
int lastTick = 0;
uint8 lastStatusByte = 0;
for (int i = 0; i < ms.getNumEvents(); ++i)
{
const MidiMessage& mm = ms.getEventPointer(i)->message;
if (! mm.isEndOfTrackMetaEvent())
{
const int tick = roundToInt (mm.getTimeStamp());
const int delta = jmax (0, tick - lastTick);
MidiFileHelpers::writeVariableLengthInt (out, (uint32) delta);
lastTick = tick;
const uint8* data = mm.getRawData();
int dataSize = mm.getRawDataSize();
const uint8 statusByte = data[0];
if (statusByte == lastStatusByte
&& (statusByte & 0xf0) != 0xf0
&& dataSize > 1
&& i > 0)
{
++data;
--dataSize;
}
else if (statusByte == 0xf0) // Write sysex message with length bytes.
{
out.writeByte ((char) statusByte);
++data;
--dataSize;
MidiFileHelpers::writeVariableLengthInt (out, (uint32) dataSize);
}
out.write (data, (size_t) dataSize);
lastStatusByte = statusByte;
}
}
{
out.writeByte (0); // (tick delta)
const MidiMessage m (MidiMessage::endOfTrack());
out.write (m.getRawData(), (size_t) m.getRawDataSize());
}
mainOut.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MTrk"));
mainOut.writeIntBigEndian ((int) out.getDataSize());
mainOut << out;
}

+ 186
- 0
source/modules/juce_audio_basics/midi/juce_MidiFile.h View File

@@ -0,0 +1,186 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_MIDIFILE_H_INCLUDED
#define JUCE_MIDIFILE_H_INCLUDED
#include "juce_MidiMessageSequence.h"
//==============================================================================
/**
Reads/writes standard midi format files.
To read a midi file, create a MidiFile object and call its readFrom() method. You
can then get the individual midi tracks from it using the getTrack() method.
To write a file, create a MidiFile object, add some MidiMessageSequence objects
to it using the addTrack() method, and then call its writeTo() method to stream
it out.
@see MidiMessageSequence
*/
class JUCE_API MidiFile
{
public:
//==============================================================================
/** Creates an empty MidiFile object.
*/
MidiFile();
/** Destructor. */
~MidiFile();
//==============================================================================
/** Returns the number of tracks in the file.
@see getTrack, addTrack
*/
int getNumTracks() const noexcept;
/** Returns a pointer to one of the tracks in the file.
@returns a pointer to the track, or nullptr if the index is out-of-range
@see getNumTracks, addTrack
*/
const MidiMessageSequence* getTrack (int index) const noexcept;
/** Adds a midi track to the file.
This will make its own internal copy of the sequence that is passed-in.
@see getNumTracks, getTrack
*/
void addTrack (const MidiMessageSequence& trackSequence);
/** Removes all midi tracks from the file.
@see getNumTracks
*/
void clear();
/** Returns the raw time format code that will be written to a stream.
After reading a midi file, this method will return the time-format that
was read from the file's header. It can be changed using the setTicksPerQuarterNote()
or setSmpteTimeFormat() methods.
If the value returned is positive, it indicates the number of midi ticks
per quarter-note - see setTicksPerQuarterNote().
It it's negative, the upper byte indicates the frames-per-second (but negative), and
the lower byte is the number of ticks per frame - see setSmpteTimeFormat().
*/
short getTimeFormat() const noexcept;
/** Sets the time format to use when this file is written to a stream.
If this is called, the file will be written as bars/beats using the
specified resolution, rather than SMPTE absolute times, as would be
used if setSmpteTimeFormat() had been called instead.
@param ticksPerQuarterNote e.g. 96, 960
@see setSmpteTimeFormat
*/
void setTicksPerQuarterNote (int ticksPerQuarterNote) noexcept;
/** Sets the time format to use when this file is written to a stream.
If this is called, the file will be written using absolute times, rather
than bars/beats as would be the case if setTicksPerBeat() had been called
instead.
@param framesPerSecond must be 24, 25, 29 or 30
@param subframeResolution the sub-second resolution, e.g. 4 (midi time code),
8, 10, 80 (SMPTE bit resolution), or 100. For millisecond
timing, setSmpteTimeFormat (25, 40)
@see setTicksPerBeat
*/
void setSmpteTimeFormat (int framesPerSecond,
int subframeResolution) noexcept;
//==============================================================================
/** Makes a list of all the tempo-change meta-events from all tracks in the midi file.
Useful for finding the positions of all the tempo changes in a file.
@param tempoChangeEvents a list to which all the events will be added
*/
void findAllTempoEvents (MidiMessageSequence& tempoChangeEvents) const;
/** Makes a list of all the time-signature meta-events from all tracks in the midi file.
Useful for finding the positions of all the tempo changes in a file.
@param timeSigEvents a list to which all the events will be added
*/
void findAllTimeSigEvents (MidiMessageSequence& timeSigEvents) const;
/** Returns the latest timestamp in any of the tracks.
(Useful for finding the length of the file).
*/
double getLastTimestamp() const;
//==============================================================================
/** Reads a midi file format stream.
After calling this, you can get the tracks that were read from the file by using the
getNumTracks() and getTrack() methods.
The timestamps of the midi events in the tracks will represent their positions in
terms of midi ticks. To convert them to seconds, use the convertTimestampTicksToSeconds()
method.
@returns true if the stream was read successfully
*/
bool readFrom (InputStream& sourceStream);
/** Writes the midi tracks as a standard midi file.
@returns true if the operation succeeded.
*/
bool writeTo (OutputStream& destStream);
/** Converts the timestamp of all the midi events from midi ticks to seconds.
This will use the midi time format and tempo/time signature info in the
tracks to convert all the timestamps to absolute values in seconds.
*/
void convertTimestampTicksToSeconds();
private:
//==============================================================================
OwnedArray <MidiMessageSequence> tracks;
short timeFormat;
void readNextTrack (const uint8* data, int size);
void writeTrack (OutputStream& mainOut, int trackNum);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiFile)
};
#endif // JUCE_MIDIFILE_H_INCLUDED

+ 183
- 0
source/modules/juce_audio_basics/midi/juce_MidiKeyboardState.cpp View File

@@ -0,0 +1,183 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
MidiKeyboardState::MidiKeyboardState()
{
zerostruct (noteStates);
}
MidiKeyboardState::~MidiKeyboardState()
{
}
//==============================================================================
void MidiKeyboardState::reset()
{
const ScopedLock sl (lock);
zerostruct (noteStates);
eventsToAdd.clear();
}
bool MidiKeyboardState::isNoteOn (const int midiChannel, const int n) const noexcept
{
jassert (midiChannel >= 0 && midiChannel <= 16);
return isPositiveAndBelow (n, (int) 128)
&& (noteStates[n] & (1 << (midiChannel - 1))) != 0;
}
bool MidiKeyboardState::isNoteOnForChannels (const int midiChannelMask, const int n) const noexcept
{
return isPositiveAndBelow (n, (int) 128)
&& (noteStates[n] & midiChannelMask) != 0;
}
void MidiKeyboardState::noteOn (const int midiChannel, const int midiNoteNumber, const float velocity)
{
jassert (midiChannel >= 0 && midiChannel <= 16);
jassert (isPositiveAndBelow (midiNoteNumber, (int) 128));
const ScopedLock sl (lock);
if (isPositiveAndBelow (midiNoteNumber, (int) 128))
{
const int timeNow = (int) Time::getMillisecondCounter();
eventsToAdd.addEvent (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity), timeNow);
eventsToAdd.clear (0, timeNow - 500);
noteOnInternal (midiChannel, midiNoteNumber, velocity);
}
}
void MidiKeyboardState::noteOnInternal (const int midiChannel, const int midiNoteNumber, const float velocity)
{
if (isPositiveAndBelow (midiNoteNumber, (int) 128))
{
noteStates [midiNoteNumber] |= (1 << (midiChannel - 1));
for (int i = listeners.size(); --i >= 0;)
listeners.getUnchecked(i)->handleNoteOn (this, midiChannel, midiNoteNumber, velocity);
}
}
void MidiKeyboardState::noteOff (const int midiChannel, const int midiNoteNumber)
{
const ScopedLock sl (lock);
if (isNoteOn (midiChannel, midiNoteNumber))
{
const int timeNow = (int) Time::getMillisecondCounter();
eventsToAdd.addEvent (MidiMessage::noteOff (midiChannel, midiNoteNumber), timeNow);
eventsToAdd.clear (0, timeNow - 500);
noteOffInternal (midiChannel, midiNoteNumber);
}
}
void MidiKeyboardState::noteOffInternal (const int midiChannel, const int midiNoteNumber)
{
if (isNoteOn (midiChannel, midiNoteNumber))
{
noteStates [midiNoteNumber] &= ~(1 << (midiChannel - 1));
for (int i = listeners.size(); --i >= 0;)
listeners.getUnchecked(i)->handleNoteOff (this, midiChannel, midiNoteNumber);
}
}
void MidiKeyboardState::allNotesOff (const int midiChannel)
{
const ScopedLock sl (lock);
if (midiChannel <= 0)
{
for (int i = 1; i <= 16; ++i)
allNotesOff (i);
}
else
{
for (int i = 0; i < 128; ++i)
noteOff (midiChannel, i);
}
}
void MidiKeyboardState::processNextMidiEvent (const MidiMessage& message)
{
if (message.isNoteOn())
{
noteOnInternal (message.getChannel(), message.getNoteNumber(), message.getFloatVelocity());
}
else if (message.isNoteOff())
{
noteOffInternal (message.getChannel(), message.getNoteNumber());
}
else if (message.isAllNotesOff())
{
for (int i = 0; i < 128; ++i)
noteOffInternal (message.getChannel(), i);
}
}
void MidiKeyboardState::processNextMidiBuffer (MidiBuffer& buffer,
const int startSample,
const int numSamples,
const bool injectIndirectEvents)
{
MidiBuffer::Iterator i (buffer);
MidiMessage message (0xf4, 0.0);
int time;
const ScopedLock sl (lock);
while (i.getNextEvent (message, time))
processNextMidiEvent (message);
if (injectIndirectEvents)
{
MidiBuffer::Iterator i2 (eventsToAdd);
const int firstEventToAdd = eventsToAdd.getFirstEventTime();
const double scaleFactor = numSamples / (double) (eventsToAdd.getLastEventTime() + 1 - firstEventToAdd);
while (i2.getNextEvent (message, time))
{
const int pos = jlimit (0, numSamples - 1, roundToInt ((time - firstEventToAdd) * scaleFactor));
buffer.addEvent (message, startSample + pos);
}
}
eventsToAdd.clear();
}
//==============================================================================
void MidiKeyboardState::addListener (MidiKeyboardStateListener* const listener)
{
const ScopedLock sl (lock);
listeners.addIfNotAlreadyThere (listener);
}
void MidiKeyboardState::removeListener (MidiKeyboardStateListener* const listener)
{
const ScopedLock sl (lock);
listeners.removeFirstMatchingValue (listener);
}

+ 206
- 0
source/modules/juce_audio_basics/midi/juce_MidiKeyboardState.h View File

@@ -0,0 +1,206 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_MIDIKEYBOARDSTATE_H_INCLUDED
#define JUCE_MIDIKEYBOARDSTATE_H_INCLUDED
#include "juce_MidiBuffer.h"
class MidiKeyboardState;
//==============================================================================
/**
Receives events from a MidiKeyboardState object.
@see MidiKeyboardState
*/
class JUCE_API MidiKeyboardStateListener
{
public:
//==============================================================================
MidiKeyboardStateListener() noexcept {}
virtual ~MidiKeyboardStateListener() {}
//==============================================================================
/** Called when one of the MidiKeyboardState's keys is pressed.
This will be called synchronously when the state is either processing a
buffer in its MidiKeyboardState::processNextMidiBuffer() method, or
when a note is being played with its MidiKeyboardState::noteOn() method.
Note that this callback could happen from an audio callback thread, so be
careful not to block, and avoid any UI activity in the callback.
*/
virtual void handleNoteOn (MidiKeyboardState* source,
int midiChannel, int midiNoteNumber, float velocity) = 0;
/** Called when one of the MidiKeyboardState's keys is released.
This will be called synchronously when the state is either processing a
buffer in its MidiKeyboardState::processNextMidiBuffer() method, or
when a note is being played with its MidiKeyboardState::noteOff() method.
Note that this callback could happen from an audio callback thread, so be
careful not to block, and avoid any UI activity in the callback.
*/
virtual void handleNoteOff (MidiKeyboardState* source,
int midiChannel, int midiNoteNumber) = 0;
};
//==============================================================================
/**
Represents a piano keyboard, keeping track of which keys are currently pressed.
This object can parse a stream of midi events, using them to update its idea
of which keys are pressed for each individiual midi channel.
When keys go up or down, it can broadcast these events to listener objects.
It also allows key up/down events to be triggered with its noteOn() and noteOff()
methods, and midi messages for these events will be merged into the
midi stream that gets processed by processNextMidiBuffer().
*/
class JUCE_API MidiKeyboardState
{
public:
//==============================================================================
MidiKeyboardState();
~MidiKeyboardState();
//==============================================================================
/** Resets the state of the object.
All internal data for all the channels is reset, but no events are sent as a
result.
If you want to release any keys that are currently down, and to send out note-up
midi messages for this, use the allNotesOff() method instead.
*/
void reset();
/** Returns true if the given midi key is currently held down for the given midi channel.
The channel number must be between 1 and 16. If you want to see if any notes are
on for a range of channels, use the isNoteOnForChannels() method.
*/
bool isNoteOn (int midiChannel, int midiNoteNumber) const noexcept;
/** Returns true if the given midi key is currently held down on any of a set of midi channels.
The channel mask has a bit set for each midi channel you want to test for - bit
0 = midi channel 1, bit 1 = midi channel 2, etc.
If a note is on for at least one of the specified channels, this returns true.
*/
bool isNoteOnForChannels (int midiChannelMask, int midiNoteNumber) const noexcept;
/** Turns a specified note on.
This will cause a suitable midi note-on event to be injected into the midi buffer during the
next call to processNextMidiBuffer().
It will also trigger a synchronous callback to the listeners to tell them that the key has
gone down.
*/
void noteOn (int midiChannel, int midiNoteNumber, float velocity);
/** Turns a specified note off.
This will cause a suitable midi note-off event to be injected into the midi buffer during the
next call to processNextMidiBuffer().
It will also trigger a synchronous callback to the listeners to tell them that the key has
gone up.
But if the note isn't acutally down for the given channel, this method will in fact do nothing.
*/
void noteOff (int midiChannel, int midiNoteNumber);
/** This will turn off any currently-down notes for the given midi channel.
If you pass 0 for the midi channel, it will in fact turn off all notes on all channels.
Calling this method will make calls to noteOff(), so can trigger synchronous callbacks
and events being added to the midi stream.
*/
void allNotesOff (int midiChannel);
//==============================================================================
/** Looks at a key-up/down event and uses it to update the state of this object.
To process a buffer full of midi messages, use the processNextMidiBuffer() method
instead.
*/
void processNextMidiEvent (const MidiMessage& message);
/** Scans a midi stream for up/down events and adds its own events to it.
This will look for any up/down events and use them to update the internal state,
synchronously making suitable callbacks to the listeners.
If injectIndirectEvents is true, then midi events to produce the recent noteOn()
and noteOff() calls will be added into the buffer.
Only the section of the buffer whose timestamps are between startSample and
(startSample + numSamples) will be affected, and any events added will be placed
between these times.
If you're going to use this method, you'll need to keep calling it regularly for
it to work satisfactorily.
To process a single midi event at a time, use the processNextMidiEvent() method
instead.
*/
void processNextMidiBuffer (MidiBuffer& buffer,
int startSample,
int numSamples,
bool injectIndirectEvents);
//==============================================================================
/** Registers a listener for callbacks when keys go up or down.
@see removeListener
*/
void addListener (MidiKeyboardStateListener* listener);
/** Deregisters a listener.
@see addListener
*/
void removeListener (MidiKeyboardStateListener* listener);
private:
//==============================================================================
CriticalSection lock;
uint16 noteStates [128];
MidiBuffer eventsToAdd;
Array <MidiKeyboardStateListener*> listeners;
void noteOnInternal (int midiChannel, int midiNoteNumber, float velocity);
void noteOffInternal (int midiChannel, int midiNoteNumber);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiKeyboardState)
};
#endif // JUCE_MIDIKEYBOARDSTATE_H_INCLUDED

+ 1048
- 0
source/modules/juce_audio_basics/midi/juce_MidiMessage.cpp
File diff suppressed because it is too large
View File


+ 939
- 0
source/modules/juce_audio_basics/midi/juce_MidiMessage.h View File

@@ -0,0 +1,939 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_MIDIMESSAGE_H_INCLUDED
#define JUCE_MIDIMESSAGE_H_INCLUDED
//==============================================================================
/**
Encapsulates a MIDI message.
@see MidiMessageSequence, MidiOutput, MidiInput
*/
class JUCE_API MidiMessage
{
public:
//==============================================================================
/** Creates a 3-byte short midi message.
@param byte1 message byte 1
@param byte2 message byte 2
@param byte3 message byte 3
@param timeStamp the time to give the midi message - this value doesn't
use any particular units, so will be application-specific
*/
MidiMessage (int byte1, int byte2, int byte3, double timeStamp = 0) noexcept;
/** Creates a 2-byte short midi message.
@param byte1 message byte 1
@param byte2 message byte 2
@param timeStamp the time to give the midi message - this value doesn't
use any particular units, so will be application-specific
*/
MidiMessage (int byte1, int byte2, double timeStamp = 0) noexcept;
/** Creates a 1-byte short midi message.
@param byte1 message byte 1
@param timeStamp the time to give the midi message - this value doesn't
use any particular units, so will be application-specific
*/
MidiMessage (int byte1, double timeStamp = 0) noexcept;
/** Creates a midi message from a block of data. */
MidiMessage (const void* data, int numBytes, double timeStamp = 0);
/** Reads the next midi message from some data.
This will read as many bytes from a data stream as it needs to make a
complete message, and will return the number of bytes it used. This lets
you read a sequence of midi messages from a file or stream.
@param data the data to read from
@param maxBytesToUse the maximum number of bytes it's allowed to read
@param numBytesUsed returns the number of bytes that were actually needed
@param lastStatusByte in a sequence of midi messages, the initial byte
can be dropped from a message if it's the same as the
first byte of the previous message, so this lets you
supply the byte to use if the first byte of the message
has in fact been dropped.
@param timeStamp the time to give the midi message - this value doesn't
use any particular units, so will be application-specific
*/
MidiMessage (const void* data, int maxBytesToUse,
int& numBytesUsed, uint8 lastStatusByte,
double timeStamp = 0);
/** Creates an active-sense message.
Since the MidiMessage has to contain a valid message, this default constructor
just initialises it with an empty sysex message.
*/
MidiMessage() noexcept;
/** Creates a copy of another midi message. */
MidiMessage (const MidiMessage& other);
/** Creates a copy of another midi message, with a different timestamp. */
MidiMessage (const MidiMessage& other, double newTimeStamp);
/** Destructor. */
~MidiMessage();
/** Copies this message from another one. */
MidiMessage& operator= (const MidiMessage& other);
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
MidiMessage (MidiMessage&& other) noexcept;
MidiMessage& operator= (MidiMessage&& other) noexcept;
#endif
//==============================================================================
/** Returns a pointer to the raw midi data.
@see getRawDataSize
*/
const uint8* getRawData() const noexcept { return data; }
/** Returns the number of bytes of data in the message.
@see getRawData
*/
int getRawDataSize() const noexcept { return size; }
//==============================================================================
/** Returns the timestamp associated with this message.
The exact meaning of this time and its units will vary, as messages are used in
a variety of different contexts.
If you're getting the message from a midi file, this could be a time in seconds, or
a number of ticks - see MidiFile::convertTimestampTicksToSeconds().
If the message is being used in a MidiBuffer, it might indicate the number of
audio samples from the start of the buffer.
If the message was created by a MidiInput, see MidiInputCallback::handleIncomingMidiMessage()
for details of the way that it initialises this value.
@see setTimeStamp, addToTimeStamp
*/
double getTimeStamp() const noexcept { return timeStamp; }
/** Changes the message's associated timestamp.
The units for the timestamp will be application-specific - see the notes for getTimeStamp().
@see addToTimeStamp, getTimeStamp
*/
void setTimeStamp (double newTimestamp) noexcept { timeStamp = newTimestamp; }
/** Adds a value to the message's timestamp.
The units for the timestamp will be application-specific.
*/
void addToTimeStamp (double delta) noexcept { timeStamp += delta; }
//==============================================================================
/** Returns the midi channel associated with the message.
@returns a value 1 to 16 if the message has a channel, or 0 if it hasn't (e.g.
if it's a sysex)
@see isForChannel, setChannel
*/
int getChannel() const noexcept;
/** Returns true if the message applies to the given midi channel.
@param channelNumber the channel number to look for, in the range 1 to 16
@see getChannel, setChannel
*/
bool isForChannel (int channelNumber) const noexcept;
/** Changes the message's midi channel.
This won't do anything for non-channel messages like sysexes.
@param newChannelNumber the channel number to change it to, in the range 1 to 16
*/
void setChannel (int newChannelNumber) noexcept;
//==============================================================================
/** Returns true if this is a system-exclusive message.
*/
bool isSysEx() const noexcept;
/** Returns a pointer to the sysex data inside the message.
If this event isn't a sysex event, it'll return 0.
@see getSysExDataSize
*/
const uint8* getSysExData() const noexcept;
/** Returns the size of the sysex data.
This value excludes the 0xf0 header byte and the 0xf7 at the end.
@see getSysExData
*/
int getSysExDataSize() const noexcept;
//==============================================================================
/** Returns true if this message is a 'key-down' event.
@param returnTrueForVelocity0 if true, then if this event is a note-on with
velocity 0, it will still be considered to be a note-on and the
method will return true. If returnTrueForVelocity0 is false, then
if this is a note-on event with velocity 0, it'll be regarded as
a note-off, and the method will return false
@see isNoteOff, getNoteNumber, getVelocity, noteOn
*/
bool isNoteOn (bool returnTrueForVelocity0 = false) const noexcept;
/** Creates a key-down message (using a floating-point velocity).
@param channel the midi channel, in the range 1 to 16
@param noteNumber the key number, 0 to 127
@param velocity in the range 0 to 1.0
@see isNoteOn
*/
static MidiMessage noteOn (int channel, int noteNumber, float velocity) noexcept;
/** Creates a key-down message (using an integer velocity).
@param channel the midi channel, in the range 1 to 16
@param noteNumber the key number, 0 to 127
@param velocity in the range 0 to 127
@see isNoteOn
*/
static MidiMessage noteOn (int channel, int noteNumber, uint8 velocity) noexcept;
/** Returns true if this message is a 'key-up' event.
If returnTrueForNoteOnVelocity0 is true, then his will also return true
for a note-on event with a velocity of 0.
@see isNoteOn, getNoteNumber, getVelocity, noteOff
*/
bool isNoteOff (bool returnTrueForNoteOnVelocity0 = true) const noexcept;
/** Creates a key-up message.
@param channel the midi channel, in the range 1 to 16
@param noteNumber the key number, 0 to 127
@param velocity in the range 0 to 127
@see isNoteOff
*/
static MidiMessage noteOff (int channel, int noteNumber, uint8 velocity = 0) noexcept;
/** Returns true if this message is a 'key-down' or 'key-up' event.
@see isNoteOn, isNoteOff
*/
bool isNoteOnOrOff() const noexcept;
/** Returns the midi note number for note-on and note-off messages.
If the message isn't a note-on or off, the value returned is undefined.
@see isNoteOff, getMidiNoteName, getMidiNoteInHertz, setNoteNumber
*/
int getNoteNumber() const noexcept;
/** Changes the midi note number of a note-on or note-off message.
If the message isn't a note on or off, this will do nothing.
*/
void setNoteNumber (int newNoteNumber) noexcept;
//==============================================================================
/** Returns the velocity of a note-on or note-off message.
The value returned will be in the range 0 to 127.
If the message isn't a note-on or off event, it will return 0.
@see getFloatVelocity
*/
uint8 getVelocity() const noexcept;
/** Returns the velocity of a note-on or note-off message.
The value returned will be in the range 0 to 1.0
If the message isn't a note-on or off event, it will return 0.
@see getVelocity, setVelocity
*/
float getFloatVelocity() const noexcept;
/** Changes the velocity of a note-on or note-off message.
If the message isn't a note on or off, this will do nothing.
@param newVelocity the new velocity, in the range 0 to 1.0
@see getFloatVelocity, multiplyVelocity
*/
void setVelocity (float newVelocity) noexcept;
/** Multiplies the velocity of a note-on or note-off message by a given amount.
If the message isn't a note on or off, this will do nothing.
@param scaleFactor the value by which to multiply the velocity
@see setVelocity
*/
void multiplyVelocity (float scaleFactor) noexcept;
//==============================================================================
/** Returns true if this message is a 'sustain pedal down' controller message. */
bool isSustainPedalOn() const noexcept;
/** Returns true if this message is a 'sustain pedal up' controller message. */
bool isSustainPedalOff() const noexcept;
/** Returns true if this message is a 'sostenuto pedal down' controller message. */
bool isSostenutoPedalOn() const noexcept;
/** Returns true if this message is a 'sostenuto pedal up' controller message. */
bool isSostenutoPedalOff() const noexcept;
/** Returns true if this message is a 'soft pedal down' controller message. */
bool isSoftPedalOn() const noexcept;
/** Returns true if this message is a 'soft pedal up' controller message. */
bool isSoftPedalOff() const noexcept;
//==============================================================================
/** Returns true if the message is a program (patch) change message.
@see getProgramChangeNumber, getGMInstrumentName
*/
bool isProgramChange() const noexcept;
/** Returns the new program number of a program change message.
If the message isn't a program change, the value returned will be
nonsense.
@see isProgramChange, getGMInstrumentName
*/
int getProgramChangeNumber() const noexcept;
/** Creates a program-change message.
@param channel the midi channel, in the range 1 to 16
@param programNumber the midi program number, 0 to 127
@see isProgramChange, getGMInstrumentName
*/
static MidiMessage programChange (int channel, int programNumber) noexcept;
//==============================================================================
/** Returns true if the message is a pitch-wheel move.
@see getPitchWheelValue, pitchWheel
*/
bool isPitchWheel() const noexcept;
/** Returns the pitch wheel position from a pitch-wheel move message.
The value returned is a 14-bit number from 0 to 0x3fff, indicating the wheel position.
If called for messages which aren't pitch wheel events, the number returned will be
nonsense.
@see isPitchWheel
*/
int getPitchWheelValue() const noexcept;
/** Creates a pitch-wheel move message.
@param channel the midi channel, in the range 1 to 16
@param position the wheel position, in the range 0 to 16383
@see isPitchWheel
*/
static MidiMessage pitchWheel (int channel, int position) noexcept;
//==============================================================================
/** Returns true if the message is an aftertouch event.
For aftertouch events, use the getNoteNumber() method to find out the key
that it applies to, and getAftertouchValue() to find out the amount. Use
getChannel() to find out the channel.
@see getAftertouchValue, getNoteNumber
*/
bool isAftertouch() const noexcept;
/** Returns the amount of aftertouch from an aftertouch messages.
The value returned is in the range 0 to 127, and will be nonsense for messages
other than aftertouch messages.
@see isAftertouch
*/
int getAfterTouchValue() const noexcept;
/** Creates an aftertouch message.
@param channel the midi channel, in the range 1 to 16
@param noteNumber the key number, 0 to 127
@param aftertouchAmount the amount of aftertouch, 0 to 127
@see isAftertouch
*/
static MidiMessage aftertouchChange (int channel,
int noteNumber,
int aftertouchAmount) noexcept;
/** Returns true if the message is a channel-pressure change event.
This is like aftertouch, but common to the whole channel rather than a specific
note. Use getChannelPressureValue() to find out the pressure, and getChannel()
to find out the channel.
@see channelPressureChange
*/
bool isChannelPressure() const noexcept;
/** Returns the pressure from a channel pressure change message.
@returns the pressure, in the range 0 to 127
@see isChannelPressure, channelPressureChange
*/
int getChannelPressureValue() const noexcept;
/** Creates a channel-pressure change event.
@param channel the midi channel: 1 to 16
@param pressure the pressure, 0 to 127
@see isChannelPressure
*/
static MidiMessage channelPressureChange (int channel, int pressure) noexcept;
//==============================================================================
/** Returns true if this is a midi controller message.
@see getControllerNumber, getControllerValue, controllerEvent
*/
bool isController() const noexcept;
/** Returns the controller number of a controller message.
The name of the controller can be looked up using the getControllerName() method.
Note that the value returned is invalid for messages that aren't controller changes.
@see isController, getControllerName, getControllerValue
*/
int getControllerNumber() const noexcept;
/** Returns the controller value from a controller message.
A value 0 to 127 is returned to indicate the new controller position.
Note that the value returned is invalid for messages that aren't controller changes.
@see isController, getControllerNumber
*/
int getControllerValue() const noexcept;
/** Returns true if this message is a controller message and if it has the specified
controller type.
*/
bool isControllerOfType (int controllerType) const noexcept;
/** Creates a controller message.
@param channel the midi channel, in the range 1 to 16
@param controllerType the type of controller
@param value the controller value
@see isController
*/
static MidiMessage controllerEvent (int channel,
int controllerType,
int value) noexcept;
/** Checks whether this message is an all-notes-off message.
@see allNotesOff
*/
bool isAllNotesOff() const noexcept;
/** Checks whether this message is an all-sound-off message.
@see allSoundOff
*/
bool isAllSoundOff() const noexcept;
/** Creates an all-notes-off message.
@param channel the midi channel, in the range 1 to 16
@see isAllNotesOff
*/
static MidiMessage allNotesOff (int channel) noexcept;
/** Creates an all-sound-off message.
@param channel the midi channel, in the range 1 to 16
@see isAllSoundOff
*/
static MidiMessage allSoundOff (int channel) noexcept;
/** Creates an all-controllers-off message.
@param channel the midi channel, in the range 1 to 16
*/
static MidiMessage allControllersOff (int channel) noexcept;
//==============================================================================
/** Returns true if this event is a meta-event.
Meta-events are things like tempo changes, track names, etc.
@see getMetaEventType, isTrackMetaEvent, isEndOfTrackMetaEvent,
isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent,
isKeySignatureMetaEvent, isMidiChannelMetaEvent
*/
bool isMetaEvent() const noexcept;
/** Returns a meta-event's type number.
If the message isn't a meta-event, this will return -1.
@see isMetaEvent, isTrackMetaEvent, isEndOfTrackMetaEvent,
isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent,
isKeySignatureMetaEvent, isMidiChannelMetaEvent
*/
int getMetaEventType() const noexcept;
/** Returns a pointer to the data in a meta-event.
@see isMetaEvent, getMetaEventLength
*/
const uint8* getMetaEventData() const noexcept;
/** Returns the length of the data for a meta-event.
@see isMetaEvent, getMetaEventData
*/
int getMetaEventLength() const noexcept;
//==============================================================================
/** Returns true if this is a 'track' meta-event. */
bool isTrackMetaEvent() const noexcept;
/** Returns true if this is an 'end-of-track' meta-event. */
bool isEndOfTrackMetaEvent() const noexcept;
/** Creates an end-of-track meta-event.
@see isEndOfTrackMetaEvent
*/
static MidiMessage endOfTrack() noexcept;
/** Returns true if this is an 'track name' meta-event.
You can use the getTextFromTextMetaEvent() method to get the track's name.
*/
bool isTrackNameEvent() const noexcept;
/** Returns true if this is a 'text' meta-event.
@see getTextFromTextMetaEvent
*/
bool isTextMetaEvent() const noexcept;
/** Returns the text from a text meta-event.
@see isTextMetaEvent
*/
String getTextFromTextMetaEvent() const;
//==============================================================================
/** Returns true if this is a 'tempo' meta-event.
@see getTempoMetaEventTickLength, getTempoSecondsPerQuarterNote
*/
bool isTempoMetaEvent() const noexcept;
/** Returns the tick length from a tempo meta-event.
@param timeFormat the 16-bit time format value from the midi file's header.
@returns the tick length (in seconds).
@see isTempoMetaEvent
*/
double getTempoMetaEventTickLength (short timeFormat) const noexcept;
/** Calculates the seconds-per-quarter-note from a tempo meta-event.
@see isTempoMetaEvent, getTempoMetaEventTickLength
*/
double getTempoSecondsPerQuarterNote() const noexcept;
/** Creates a tempo meta-event.
@see isTempoMetaEvent
*/
static MidiMessage tempoMetaEvent (int microsecondsPerQuarterNote) noexcept;
//==============================================================================
/** Returns true if this is a 'time-signature' meta-event.
@see getTimeSignatureInfo
*/
bool isTimeSignatureMetaEvent() const noexcept;
/** Returns the time-signature values from a time-signature meta-event.
@see isTimeSignatureMetaEvent
*/
void getTimeSignatureInfo (int& numerator, int& denominator) const noexcept;
/** Creates a time-signature meta-event.
@see isTimeSignatureMetaEvent
*/
static MidiMessage timeSignatureMetaEvent (int numerator, int denominator);
//==============================================================================
/** Returns true if this is a 'key-signature' meta-event.
@see getKeySignatureNumberOfSharpsOrFlats
*/
bool isKeySignatureMetaEvent() const noexcept;
/** Returns the key from a key-signature meta-event.
@see isKeySignatureMetaEvent
*/
int getKeySignatureNumberOfSharpsOrFlats() const noexcept;
//==============================================================================
/** Returns true if this is a 'channel' meta-event.
A channel meta-event specifies the midi channel that should be used
for subsequent meta-events.
@see getMidiChannelMetaEventChannel
*/
bool isMidiChannelMetaEvent() const noexcept;
/** Returns the channel number from a channel meta-event.
@returns the channel, in the range 1 to 16.
@see isMidiChannelMetaEvent
*/
int getMidiChannelMetaEventChannel() const noexcept;
/** Creates a midi channel meta-event.
@param channel the midi channel, in the range 1 to 16
@see isMidiChannelMetaEvent
*/
static MidiMessage midiChannelMetaEvent (int channel) noexcept;
//==============================================================================
/** Returns true if this is an active-sense message. */
bool isActiveSense() const noexcept;
//==============================================================================
/** Returns true if this is a midi start event.
@see midiStart
*/
bool isMidiStart() const noexcept;
/** Creates a midi start event. */
static MidiMessage midiStart() noexcept;
/** Returns true if this is a midi continue event.
@see midiContinue
*/
bool isMidiContinue() const noexcept;
/** Creates a midi continue event. */
static MidiMessage midiContinue() noexcept;
/** Returns true if this is a midi stop event.
@see midiStop
*/
bool isMidiStop() const noexcept;
/** Creates a midi stop event. */
static MidiMessage midiStop() noexcept;
/** Returns true if this is a midi clock event.
@see midiClock, songPositionPointer
*/
bool isMidiClock() const noexcept;
/** Creates a midi clock event. */
static MidiMessage midiClock() noexcept;
/** Returns true if this is a song-position-pointer message.
@see getSongPositionPointerMidiBeat, songPositionPointer
*/
bool isSongPositionPointer() const noexcept;
/** Returns the midi beat-number of a song-position-pointer message.
@see isSongPositionPointer, songPositionPointer
*/
int getSongPositionPointerMidiBeat() const noexcept;
/** Creates a song-position-pointer message.
The position is a number of midi beats from the start of the song, where 1 midi
beat is 6 midi clocks, and there are 24 midi clocks in a quarter-note. So there
are 4 midi beats in a quarter-note.
@see isSongPositionPointer, getSongPositionPointerMidiBeat
*/
static MidiMessage songPositionPointer (int positionInMidiBeats) noexcept;
//==============================================================================
/** Returns true if this is a quarter-frame midi timecode message.
@see quarterFrame, getQuarterFrameSequenceNumber, getQuarterFrameValue
*/
bool isQuarterFrame() const noexcept;
/** Returns the sequence number of a quarter-frame midi timecode message.
This will be a value between 0 and 7.
@see isQuarterFrame, getQuarterFrameValue, quarterFrame
*/
int getQuarterFrameSequenceNumber() const noexcept;
/** Returns the value from a quarter-frame message.
This will be the lower nybble of the message's data-byte, a value
between 0 and 15
*/
int getQuarterFrameValue() const noexcept;
/** Creates a quarter-frame MTC message.
@param sequenceNumber a value 0 to 7 for the upper nybble of the message's data byte
@param value a value 0 to 15 for the lower nybble of the message's data byte
*/
static MidiMessage quarterFrame (int sequenceNumber, int value) noexcept;
/** SMPTE timecode types.
Used by the getFullFrameParameters() and fullFrame() methods.
*/
enum SmpteTimecodeType
{
fps24 = 0,
fps25 = 1,
fps30drop = 2,
fps30 = 3
};
/** Returns true if this is a full-frame midi timecode message.
*/
bool isFullFrame() const noexcept;
/** Extracts the timecode information from a full-frame midi timecode message.
You should only call this on messages where you've used isFullFrame() to
check that they're the right kind.
*/
void getFullFrameParameters (int& hours,
int& minutes,
int& seconds,
int& frames,
SmpteTimecodeType& timecodeType) const noexcept;
/** Creates a full-frame MTC message.
*/
static MidiMessage fullFrame (int hours,
int minutes,
int seconds,
int frames,
SmpteTimecodeType timecodeType);
//==============================================================================
/** Types of MMC command.
@see isMidiMachineControlMessage, getMidiMachineControlCommand, midiMachineControlCommand
*/
enum MidiMachineControlCommand
{
mmc_stop = 1,
mmc_play = 2,
mmc_deferredplay = 3,
mmc_fastforward = 4,
mmc_rewind = 5,
mmc_recordStart = 6,
mmc_recordStop = 7,
mmc_pause = 9
};
/** Checks whether this is an MMC message.
If it is, you can use the getMidiMachineControlCommand() to find out its type.
*/
bool isMidiMachineControlMessage() const noexcept;
/** For an MMC message, this returns its type.
Make sure it's actually an MMC message with isMidiMachineControlMessage() before
calling this method.
*/
MidiMachineControlCommand getMidiMachineControlCommand() const noexcept;
/** Creates an MMC message.
*/
static MidiMessage midiMachineControlCommand (MidiMachineControlCommand command);
/** Checks whether this is an MMC "goto" message.
If it is, the parameters passed-in are set to the time that the message contains.
@see midiMachineControlGoto
*/
bool isMidiMachineControlGoto (int& hours,
int& minutes,
int& seconds,
int& frames) const noexcept;
/** Creates an MMC "goto" message.
This messages tells the device to go to a specific frame.
@see isMidiMachineControlGoto
*/
static MidiMessage midiMachineControlGoto (int hours,
int minutes,
int seconds,
int frames);
//==============================================================================
/** Creates a master-volume change message.
@param volume the volume, 0 to 1.0
*/
static MidiMessage masterVolume (float volume);
//==============================================================================
/** Creates a system-exclusive message.
The data passed in is wrapped with header and tail bytes of 0xf0 and 0xf7.
*/
static MidiMessage createSysExMessage (const void* sysexData,
int dataSize);
//==============================================================================
/** Reads a midi variable-length integer.
@param data the data to read the number from
@param numBytesUsed on return, this will be set to the number of bytes that were read
*/
static int readVariableLengthVal (const uint8* data,
int& numBytesUsed) noexcept;
/** Based on the first byte of a short midi message, this uses a lookup table
to return the message length (either 1, 2, or 3 bytes).
The value passed in must be 0x80 or higher.
*/
static int getMessageLengthFromFirstByte (const uint8 firstByte) noexcept;
//==============================================================================
/** Returns the name of a midi note number.
E.g "C", "D#", etc.
@param noteNumber the midi note number, 0 to 127
@param useSharps if true, sharpened notes are used, e.g. "C#", otherwise
they'll be flattened, e.g. "Db"
@param includeOctaveNumber if true, the octave number will be appended to the string,
e.g. "C#4"
@param octaveNumForMiddleC if an octave number is being appended, this indicates the
number that will be used for middle C's octave
@see getMidiNoteInHertz
*/
static String getMidiNoteName (int noteNumber,
bool useSharps,
bool includeOctaveNumber,
int octaveNumForMiddleC);
/** Returns the frequency of a midi note number.
The frequencyOfA parameter is an optional frequency for 'A', normally 440-444Hz for concert pitch.
@see getMidiNoteName
*/
static double getMidiNoteInHertz (int noteNumber, const double frequencyOfA = 440.0) noexcept;
/** Returns the standard name of a GM instrument.
@param midiInstrumentNumber the program number 0 to 127
@see getProgramChangeNumber
*/
static String getGMInstrumentName (int midiInstrumentNumber);
/** Returns the name of a bank of GM instruments.
@param midiBankNumber the bank, 0 to 15
*/
static String getGMInstrumentBankName (int midiBankNumber);
/** Returns the standard name of a channel 10 percussion sound.
@param midiNoteNumber the key number, 35 to 81
*/
static String getRhythmInstrumentName (int midiNoteNumber);
/** Returns the name of a controller type number.
@see getControllerNumber
*/
static String getControllerName (int controllerNumber);
private:
//==============================================================================
double timeStamp;
uint8* data;
int size;
#ifndef DOXYGEN
union
{
uint8 asBytes[4];
uint32 asInt32;
} preallocatedData;
#endif
void freeData() noexcept;
void setToUseInternalData() noexcept;
bool usesAllocatedData() const noexcept;
};
#endif // JUCE_MIDIMESSAGE_H_INCLUDED

+ 334
- 0
source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp View File

@@ -0,0 +1,334 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
MidiMessageSequence::MidiMessageSequence()
{
}
MidiMessageSequence::MidiMessageSequence (const MidiMessageSequence& other)
{
list.ensureStorageAllocated (other.list.size());
for (int i = 0; i < other.list.size(); ++i)
list.add (new MidiEventHolder (other.list.getUnchecked(i)->message));
}
MidiMessageSequence& MidiMessageSequence::operator= (const MidiMessageSequence& other)
{
MidiMessageSequence otherCopy (other);
swapWith (otherCopy);
return *this;
}
void MidiMessageSequence::swapWith (MidiMessageSequence& other) noexcept
{
list.swapWith (other.list);
}
MidiMessageSequence::~MidiMessageSequence()
{
}
void MidiMessageSequence::clear()
{
list.clear();
}
int MidiMessageSequence::getNumEvents() const
{
return list.size();
}
MidiMessageSequence::MidiEventHolder* MidiMessageSequence::getEventPointer (const int index) const
{
return list [index];
}
double MidiMessageSequence::getTimeOfMatchingKeyUp (const int index) const
{
if (const MidiEventHolder* const meh = list [index])
if (meh->noteOffObject != nullptr)
return meh->noteOffObject->message.getTimeStamp();
return 0.0;
}
int MidiMessageSequence::getIndexOfMatchingKeyUp (const int index) const
{
if (const MidiEventHolder* const meh = list [index])
return list.indexOf (meh->noteOffObject);
return -1;
}
int MidiMessageSequence::getIndexOf (MidiEventHolder* const event) const
{
return list.indexOf (event);
}
int MidiMessageSequence::getNextIndexAtTime (const double timeStamp) const
{
const int numEvents = list.size();
int i;
for (i = 0; i < numEvents; ++i)
if (list.getUnchecked(i)->message.getTimeStamp() >= timeStamp)
break;
return i;
}
//==============================================================================
double MidiMessageSequence::getStartTime() const
{
return getEventTime (0);
}
double MidiMessageSequence::getEndTime() const
{
return getEventTime (list.size() - 1);
}
double MidiMessageSequence::getEventTime (const int index) const
{
if (const MidiEventHolder* const meh = list [index])
return meh->message.getTimeStamp();
return 0.0;
}
//==============================================================================
MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (const MidiMessage& newMessage,
double timeAdjustment)
{
MidiEventHolder* const newOne = new MidiEventHolder (newMessage);
timeAdjustment += newMessage.getTimeStamp();
newOne->message.setTimeStamp (timeAdjustment);
int i;
for (i = list.size(); --i >= 0;)
if (list.getUnchecked(i)->message.getTimeStamp() <= timeAdjustment)
break;
list.insert (i + 1, newOne);
return newOne;
}
void MidiMessageSequence::deleteEvent (const int index,
const bool deleteMatchingNoteUp)
{
if (isPositiveAndBelow (index, list.size()))
{
if (deleteMatchingNoteUp)
deleteEvent (getIndexOfMatchingKeyUp (index), false);
list.remove (index);
}
}
struct MidiMessageSequenceSorter
{
static int compareElements (const MidiMessageSequence::MidiEventHolder* const first,
const MidiMessageSequence::MidiEventHolder* const second) noexcept
{
const double diff = first->message.getTimeStamp() - second->message.getTimeStamp();
return (diff > 0) - (diff < 0);
}
};
void MidiMessageSequence::addSequence (const MidiMessageSequence& other,
double timeAdjustment,
double firstAllowableTime,
double endOfAllowableDestTimes)
{
firstAllowableTime -= timeAdjustment;
endOfAllowableDestTimes -= timeAdjustment;
for (int i = 0; i < other.list.size(); ++i)
{
const MidiMessage& m = other.list.getUnchecked(i)->message;
const double t = m.getTimeStamp();
if (t >= firstAllowableTime && t < endOfAllowableDestTimes)
{
MidiEventHolder* const newOne = new MidiEventHolder (m);
newOne->message.setTimeStamp (timeAdjustment + t);
list.add (newOne);
}
}
sort();
}
//==============================================================================
void MidiMessageSequence::sort()
{
MidiMessageSequenceSorter sorter;
list.sort (sorter, true);
}
void MidiMessageSequence::updateMatchedPairs()
{
for (int i = 0; i < list.size(); ++i)
{
MidiEventHolder* const meh = list.getUnchecked(i);
const MidiMessage& m1 = meh->message;
if (m1.isNoteOn())
{
meh->noteOffObject = nullptr;
const int note = m1.getNoteNumber();
const int chan = m1.getChannel();
const int len = list.size();
for (int j = i + 1; j < len; ++j)
{
const MidiMessage& m = list.getUnchecked(j)->message;
if (m.getNoteNumber() == note && m.getChannel() == chan)
{
if (m.isNoteOff())
{
meh->noteOffObject = list[j];
break;
}
else if (m.isNoteOn())
{
MidiEventHolder* const newEvent = new MidiEventHolder (MidiMessage::noteOff (chan, note));
list.insert (j, newEvent);
newEvent->message.setTimeStamp (m.getTimeStamp());
meh->noteOffObject = newEvent;
break;
}
}
}
}
}
}
void MidiMessageSequence::addTimeToMessages (const double delta)
{
for (int i = list.size(); --i >= 0;)
{
MidiMessage& mm = list.getUnchecked(i)->message;
mm.setTimeStamp (mm.getTimeStamp() + delta);
}
}
//==============================================================================
void MidiMessageSequence::extractMidiChannelMessages (const int channelNumberToExtract,
MidiMessageSequence& destSequence,
const bool alsoIncludeMetaEvents) const
{
for (int i = 0; i < list.size(); ++i)
{
const MidiMessage& mm = list.getUnchecked(i)->message;
if (mm.isForChannel (channelNumberToExtract) || (alsoIncludeMetaEvents && mm.isMetaEvent()))
destSequence.addEvent (mm);
}
}
void MidiMessageSequence::extractSysExMessages (MidiMessageSequence& destSequence) const
{
for (int i = 0; i < list.size(); ++i)
{
const MidiMessage& mm = list.getUnchecked(i)->message;
if (mm.isSysEx())
destSequence.addEvent (mm);
}
}
void MidiMessageSequence::deleteMidiChannelMessages (const int channelNumberToRemove)
{
for (int i = list.size(); --i >= 0;)
if (list.getUnchecked(i)->message.isForChannel (channelNumberToRemove))
list.remove(i);
}
void MidiMessageSequence::deleteSysExMessages()
{
for (int i = list.size(); --i >= 0;)
if (list.getUnchecked(i)->message.isSysEx())
list.remove(i);
}
//==============================================================================
void MidiMessageSequence::createControllerUpdatesForTime (const int channelNumber,
const double time,
OwnedArray<MidiMessage>& dest)
{
bool doneProg = false;
bool donePitchWheel = false;
Array <int> doneControllers;
doneControllers.ensureStorageAllocated (32);
for (int i = list.size(); --i >= 0;)
{
const MidiMessage& mm = list.getUnchecked(i)->message;
if (mm.isForChannel (channelNumber) && mm.getTimeStamp() <= time)
{
if (mm.isProgramChange())
{
if (! doneProg)
{
dest.add (new MidiMessage (mm, 0.0));
doneProg = true;
}
}
else if (mm.isController())
{
if (! doneControllers.contains (mm.getControllerNumber()))
{
dest.add (new MidiMessage (mm, 0.0));
doneControllers.add (mm.getControllerNumber());
}
}
else if (mm.isPitchWheel())
{
if (! donePitchWheel)
{
dest.add (new MidiMessage (mm, 0.0));
donePitchWheel = true;
}
}
}
}
}
//==============================================================================
MidiMessageSequence::MidiEventHolder::MidiEventHolder (const MidiMessage& mm)
: message (mm),
noteOffObject (nullptr)
{
}
MidiMessageSequence::MidiEventHolder::~MidiEventHolder()
{
}

+ 280
- 0
source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h View File

@@ -0,0 +1,280 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_MIDIMESSAGESEQUENCE_H_INCLUDED
#define JUCE_MIDIMESSAGESEQUENCE_H_INCLUDED
#include "juce_MidiMessage.h"
//==============================================================================
/**
A sequence of timestamped midi messages.
This allows the sequence to be manipulated, and also to be read from and
written to a standard midi file.
@see MidiMessage, MidiFile
*/
class JUCE_API MidiMessageSequence
{
public:
//==============================================================================
/** Creates an empty midi sequence object. */
MidiMessageSequence();
/** Creates a copy of another sequence. */
MidiMessageSequence (const MidiMessageSequence& other);
/** Replaces this sequence with another one. */
MidiMessageSequence& operator= (const MidiMessageSequence& other);
/** Destructor. */
~MidiMessageSequence();
//==============================================================================
/** Structure used to hold midi events in the sequence.
These structures act as 'handles' on the events as they are moved about in
the list, and make it quick to find the matching note-offs for note-on events.
@see MidiMessageSequence::getEventPointer
*/
class MidiEventHolder
{
public:
//==============================================================================
/** Destructor. */
~MidiEventHolder();
/** The message itself, whose timestamp is used to specify the event's time.
*/
MidiMessage message;
/** The matching note-off event (if this is a note-on event).
If this isn't a note-on, this pointer will be null.
Use the MidiMessageSequence::updateMatchedPairs() method to keep these
note-offs up-to-date after events have been moved around in the sequence
or deleted.
*/
MidiEventHolder* noteOffObject;
private:
//==============================================================================
friend class MidiMessageSequence;
MidiEventHolder (const MidiMessage& message);
JUCE_LEAK_DETECTOR (MidiEventHolder)
};
//==============================================================================
/** Clears the sequence. */
void clear();
/** Returns the number of events in the sequence. */
int getNumEvents() const;
/** Returns a pointer to one of the events. */
MidiEventHolder* getEventPointer (int index) const;
/** Returns the time of the note-up that matches the note-on at this index.
If the event at this index isn't a note-on, it'll just return 0.
@see MidiMessageSequence::MidiEventHolder::noteOffObject
*/
double getTimeOfMatchingKeyUp (int index) const;
/** Returns the index of the note-up that matches the note-on at this index.
If the event at this index isn't a note-on, it'll just return -1.
@see MidiMessageSequence::MidiEventHolder::noteOffObject
*/
int getIndexOfMatchingKeyUp (int index) const;
/** Returns the index of an event. */
int getIndexOf (MidiEventHolder* event) const;
/** Returns the index of the first event on or after the given timestamp.
If the time is beyond the end of the sequence, this will return the
number of events.
*/
int getNextIndexAtTime (double timeStamp) const;
//==============================================================================
/** Returns the timestamp of the first event in the sequence.
@see getEndTime
*/
double getStartTime() const;
/** Returns the timestamp of the last event in the sequence.
@see getStartTime
*/
double getEndTime() const;
/** Returns the timestamp of the event at a given index.
If the index is out-of-range, this will return 0.0
*/
double getEventTime (int index) const;
//==============================================================================
/** Inserts a midi message into the sequence.
The index at which the new message gets inserted will depend on its timestamp,
because the sequence is kept sorted.
Remember to call updateMatchedPairs() after adding note-on events.
@param newMessage the new message to add (an internal copy will be made)
@param timeAdjustment an optional value to add to the timestamp of the message
that will be inserted
@see updateMatchedPairs
*/
MidiEventHolder* addEvent (const MidiMessage& newMessage,
double timeAdjustment = 0);
/** Deletes one of the events in the sequence.
Remember to call updateMatchedPairs() after removing events.
@param index the index of the event to delete
@param deleteMatchingNoteUp whether to also remove the matching note-off
if the event you're removing is a note-on
*/
void deleteEvent (int index, bool deleteMatchingNoteUp);
/** Merges another sequence into this one.
Remember to call updateMatchedPairs() after using this method.
@param other the sequence to add from
@param timeAdjustmentDelta an amount to add to the timestamps of the midi events
as they are read from the other sequence
@param firstAllowableDestTime events will not be added if their time is earlier
than this time. (This is after their time has been adjusted
by the timeAdjustmentDelta)
@param endOfAllowableDestTimes events will not be added if their time is equal to
or greater than this time. (This is after their time has
been adjusted by the timeAdjustmentDelta)
*/
void addSequence (const MidiMessageSequence& other,
double timeAdjustmentDelta,
double firstAllowableDestTime,
double endOfAllowableDestTimes);
//==============================================================================
/** Makes sure all the note-on and note-off pairs are up-to-date.
Call this after moving messages about or deleting/adding messages, and it
will scan the list and make sure all the note-offs in the MidiEventHolder
structures are pointing at the correct ones.
*/
void updateMatchedPairs();
/** Forces a sort of the sequence.
You may need to call this if you've manually modified the timestamps of some
events such that the overall order now needs updating.
*/
void sort();
//==============================================================================
/** Copies all the messages for a particular midi channel to another sequence.
@param channelNumberToExtract the midi channel to look for, in the range 1 to 16
@param destSequence the sequence that the chosen events should be copied to
@param alsoIncludeMetaEvents if true, any meta-events (which don't apply to a specific
channel) will also be copied across.
@see extractSysExMessages
*/
void extractMidiChannelMessages (int channelNumberToExtract,
MidiMessageSequence& destSequence,
bool alsoIncludeMetaEvents) const;
/** Copies all midi sys-ex messages to another sequence.
@param destSequence this is the sequence to which any sys-exes in this sequence
will be added
@see extractMidiChannelMessages
*/
void extractSysExMessages (MidiMessageSequence& destSequence) const;
/** Removes any messages in this sequence that have a specific midi channel.
@param channelNumberToRemove the midi channel to look for, in the range 1 to 16
*/
void deleteMidiChannelMessages (int channelNumberToRemove);
/** Removes any sys-ex messages from this sequence.
*/
void deleteSysExMessages();
/** Adds an offset to the timestamps of all events in the sequence.
@param deltaTime the amount to add to each timestamp.
*/
void addTimeToMessages (double deltaTime);
//==============================================================================
/** Scans through the sequence to determine the state of any midi controllers at
a given time.
This will create a sequence of midi controller changes that can be
used to set all midi controllers to the state they would be in at the
specified time within this sequence.
As well as controllers, it will also recreate the midi program number
and pitch bend position.
@param channelNumber the midi channel to look for, in the range 1 to 16. Controllers
for other channels will be ignored.
@param time the time at which you want to find out the state - there are
no explicit units for this time measurement, it's the same units
as used for the timestamps of the messages
@param resultMessages an array to which midi controller-change messages will be added. This
will be the minimum number of controller changes to recreate the
state at the required time.
*/
void createControllerUpdatesForTime (int channelNumber, double time,
OwnedArray<MidiMessage>& resultMessages);
//==============================================================================
/** Swaps this sequence with another one. */
void swapWith (MidiMessageSequence& other) noexcept;
private:
//==============================================================================
friend class MidiFile;
OwnedArray <MidiEventHolder> list;
JUCE_LEAK_DETECTOR (MidiMessageSequence)
};
#endif // JUCE_MIDIMESSAGESEQUENCE_H_INCLUDED

+ 183
- 0
source/modules/juce_audio_basics/sources/juce_AudioSource.h View File

@@ -0,0 +1,183 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_AUDIOSOURCE_H_INCLUDED
#define JUCE_AUDIOSOURCE_H_INCLUDED
#include "../buffers/juce_AudioSampleBuffer.h"
//==============================================================================
/**
Used by AudioSource::getNextAudioBlock().
*/
struct JUCE_API AudioSourceChannelInfo
{
/** Creates an uninitialised AudioSourceChannelInfo. */
AudioSourceChannelInfo() noexcept
{
}
/** Creates an AudioSourceChannelInfo. */
AudioSourceChannelInfo (AudioSampleBuffer* bufferToUse,
int startSampleOffset, int numSamplesToUse) noexcept
: buffer (bufferToUse),
startSample (startSampleOffset),
numSamples (numSamplesToUse)
{
}
/** Creates an AudioSourceChannelInfo that uses the whole of a buffer.
Note that the buffer provided must not be deleted while the
AudioSourceChannelInfo is still using it.
*/
explicit AudioSourceChannelInfo (AudioSampleBuffer& bufferToUse) noexcept
: buffer (&bufferToUse),
startSample (0),
numSamples (bufferToUse.getNumSamples())
{
}
/** The destination buffer to fill with audio data.
When the AudioSource::getNextAudioBlock() method is called, the active section
of this buffer should be filled with whatever output the source produces.
Only the samples specified by the startSample and numSamples members of this structure
should be affected by the call.
The contents of the buffer when it is passed to the the AudioSource::getNextAudioBlock()
method can be treated as the input if the source is performing some kind of filter operation,
but should be cleared if this is not the case - the clearActiveBufferRegion() is
a handy way of doing this.
The number of channels in the buffer could be anything, so the AudioSource
must cope with this in whatever way is appropriate for its function.
*/
AudioSampleBuffer* buffer;
/** The first sample in the buffer from which the callback is expected
to write data. */
int startSample;
/** The number of samples in the buffer which the callback is expected to
fill with data. */
int numSamples;
/** Convenient method to clear the buffer if the source is not producing any data. */
void clearActiveBufferRegion() const
{
if (buffer != nullptr)
buffer->clear (startSample, numSamples);
}
};
//==============================================================================
/**
Base class for objects that can produce a continuous stream of audio.
An AudioSource has two states: 'prepared' and 'unprepared'.
When a source needs to be played, it is first put into a 'prepared' state by a call to
prepareToPlay(), and then repeated calls will be made to its getNextAudioBlock() method to
process the audio data.
Once playback has finished, the releaseResources() method is called to put the stream
back into an 'unprepared' state.
@see AudioFormatReaderSource, ResamplingAudioSource
*/
class JUCE_API AudioSource
{
protected:
//==============================================================================
/** Creates an AudioSource. */
AudioSource() noexcept {}
public:
/** Destructor. */
virtual ~AudioSource() {}
//==============================================================================
/** Tells the source to prepare for playing.
An AudioSource has two states: prepared and unprepared.
The prepareToPlay() method is guaranteed to be called at least once on an 'unpreprared'
source to put it into a 'prepared' state before any calls will be made to getNextAudioBlock().
This callback allows the source to initialise any resources it might need when playing.
Once playback has finished, the releaseResources() method is called to put the stream
back into an 'unprepared' state.
Note that this method could be called more than once in succession without
a matching call to releaseResources(), so make sure your code is robust and
can handle that kind of situation.
@param samplesPerBlockExpected the number of samples that the source
will be expected to supply each time its
getNextAudioBlock() method is called. This
number may vary slightly, because it will be dependent
on audio hardware callbacks, and these aren't
guaranteed to always use a constant block size, so
the source should be able to cope with small variations.
@param sampleRate the sample rate that the output will be used at - this
is needed by sources such as tone generators.
@see releaseResources, getNextAudioBlock
*/
virtual void prepareToPlay (int samplesPerBlockExpected,
double sampleRate) = 0;
/** Allows the source to release anything it no longer needs after playback has stopped.
This will be called when the source is no longer going to have its getNextAudioBlock()
method called, so it should release any spare memory, etc. that it might have
allocated during the prepareToPlay() call.
Note that there's no guarantee that prepareToPlay() will actually have been called before
releaseResources(), and it may be called more than once in succession, so make sure your
code is robust and doesn't make any assumptions about when it will be called.
@see prepareToPlay, getNextAudioBlock
*/
virtual void releaseResources() = 0;
/** Called repeatedly to fetch subsequent blocks of audio data.
After calling the prepareToPlay() method, this callback will be made each
time the audio playback hardware (or whatever other destination the audio
data is going to) needs another block of data.
It will generally be called on a high-priority system thread, or possibly even
an interrupt, so be careful not to do too much work here, as that will cause
audio glitches!
@see AudioSourceChannelInfo, prepareToPlay, releaseResources
*/
virtual void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) = 0;
};
#endif // JUCE_AUDIOSOURCE_H_INCLUDED

+ 260
- 0
source/modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp View File

@@ -0,0 +1,260 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
BufferingAudioSource::BufferingAudioSource (PositionableAudioSource* source_,
TimeSliceThread& backgroundThread_,
const bool deleteSourceWhenDeleted,
const int numberOfSamplesToBuffer_,
const int numberOfChannels_)
: source (source_, deleteSourceWhenDeleted),
backgroundThread (backgroundThread_),
numberOfSamplesToBuffer (jmax (1024, numberOfSamplesToBuffer_)),
numberOfChannels (numberOfChannels_),
buffer (numberOfChannels_, 0),
bufferValidStart (0),
bufferValidEnd (0),
nextPlayPos (0),
sampleRate (0),
wasSourceLooping (false),
isPrepared (false)
{
jassert (source_ != nullptr);
jassert (numberOfSamplesToBuffer_ > 1024); // not much point using this class if you're
// not using a larger buffer..
}
BufferingAudioSource::~BufferingAudioSource()
{
releaseResources();
}
//==============================================================================
void BufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate_)
{
const int bufferSizeNeeded = jmax (samplesPerBlockExpected * 2, numberOfSamplesToBuffer);
if (sampleRate_ != sampleRate
|| bufferSizeNeeded != buffer.getNumSamples()
|| ! isPrepared)
{
backgroundThread.removeTimeSliceClient (this);
isPrepared = true;
sampleRate = sampleRate_;
source->prepareToPlay (samplesPerBlockExpected, sampleRate_);
buffer.setSize (numberOfChannels, bufferSizeNeeded);
buffer.clear();
bufferValidStart = 0;
bufferValidEnd = 0;
backgroundThread.addTimeSliceClient (this);
while (bufferValidEnd - bufferValidStart < jmin (((int) sampleRate_) / 4,
buffer.getNumSamples() / 2))
{
backgroundThread.moveToFrontOfQueue (this);
Thread::sleep (5);
}
}
}
void BufferingAudioSource::releaseResources()
{
isPrepared = false;
backgroundThread.removeTimeSliceClient (this);
buffer.setSize (numberOfChannels, 0);
source->releaseResources();
}
void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info)
{
const ScopedLock sl (bufferStartPosLock);
const int validStart = (int) (jlimit (bufferValidStart, bufferValidEnd, nextPlayPos) - nextPlayPos);
const int validEnd = (int) (jlimit (bufferValidStart, bufferValidEnd, nextPlayPos + info.numSamples) - nextPlayPos);
if (validStart == validEnd)
{
// total cache miss
info.clearActiveBufferRegion();
}
else
{
if (validStart > 0)
info.buffer->clear (info.startSample, validStart); // partial cache miss at start
if (validEnd < info.numSamples)
info.buffer->clear (info.startSample + validEnd,
info.numSamples - validEnd); // partial cache miss at end
if (validStart < validEnd)
{
for (int chan = jmin (numberOfChannels, info.buffer->getNumChannels()); --chan >= 0;)
{
jassert (buffer.getNumSamples() > 0);
const int startBufferIndex = (int) ((validStart + nextPlayPos) % buffer.getNumSamples());
const int endBufferIndex = (int) ((validEnd + nextPlayPos) % buffer.getNumSamples());
if (startBufferIndex < endBufferIndex)
{
info.buffer->copyFrom (chan, info.startSample + validStart,
buffer,
chan, startBufferIndex,
validEnd - validStart);
}
else
{
const int initialSize = buffer.getNumSamples() - startBufferIndex;
info.buffer->copyFrom (chan, info.startSample + validStart,
buffer,
chan, startBufferIndex,
initialSize);
info.buffer->copyFrom (chan, info.startSample + validStart + initialSize,
buffer,
chan, 0,
(validEnd - validStart) - initialSize);
}
}
}
nextPlayPos += info.numSamples;
}
}
int64 BufferingAudioSource::getNextReadPosition() const
{
jassert (source->getTotalLength() > 0);
return (source->isLooping() && nextPlayPos > 0)
? nextPlayPos % source->getTotalLength()
: nextPlayPos;
}
void BufferingAudioSource::setNextReadPosition (int64 newPosition)
{
const ScopedLock sl (bufferStartPosLock);
nextPlayPos = newPosition;
backgroundThread.moveToFrontOfQueue (this);
}
bool BufferingAudioSource::readNextBufferChunk()
{
int64 newBVS, newBVE, sectionToReadStart, sectionToReadEnd;
{
const ScopedLock sl (bufferStartPosLock);
if (wasSourceLooping != isLooping())
{
wasSourceLooping = isLooping();
bufferValidStart = 0;
bufferValidEnd = 0;
}
newBVS = jmax ((int64) 0, nextPlayPos);
newBVE = newBVS + buffer.getNumSamples() - 4;
sectionToReadStart = 0;
sectionToReadEnd = 0;
const int maxChunkSize = 2048;
if (newBVS < bufferValidStart || newBVS >= bufferValidEnd)
{
newBVE = jmin (newBVE, newBVS + maxChunkSize);
sectionToReadStart = newBVS;
sectionToReadEnd = newBVE;
bufferValidStart = 0;
bufferValidEnd = 0;
}
else if (std::abs ((int) (newBVS - bufferValidStart)) > 512
|| std::abs ((int) (newBVE - bufferValidEnd)) > 512)
{
newBVE = jmin (newBVE, bufferValidEnd + maxChunkSize);
sectionToReadStart = bufferValidEnd;
sectionToReadEnd = newBVE;
bufferValidStart = newBVS;
bufferValidEnd = jmin (bufferValidEnd, newBVE);
}
}
if (sectionToReadStart == sectionToReadEnd)
return false;
jassert (buffer.getNumSamples() > 0);
const int bufferIndexStart = (int) (sectionToReadStart % buffer.getNumSamples());
const int bufferIndexEnd = (int) (sectionToReadEnd % buffer.getNumSamples());
if (bufferIndexStart < bufferIndexEnd)
{
readBufferSection (sectionToReadStart,
(int) (sectionToReadEnd - sectionToReadStart),
bufferIndexStart);
}
else
{
const int initialSize = buffer.getNumSamples() - bufferIndexStart;
readBufferSection (sectionToReadStart,
initialSize,
bufferIndexStart);
readBufferSection (sectionToReadStart + initialSize,
(int) (sectionToReadEnd - sectionToReadStart) - initialSize,
0);
}
{
const ScopedLock sl2 (bufferStartPosLock);
bufferValidStart = newBVS;
bufferValidEnd = newBVE;
}
return true;
}
void BufferingAudioSource::readBufferSection (const int64 start, const int length, const int bufferOffset)
{
if (source->getNextReadPosition() != start)
source->setNextReadPosition (start);
AudioSourceChannelInfo info (&buffer, bufferOffset, length);
source->getNextAudioBlock (info);
}
int BufferingAudioSource::useTimeSlice()
{
return readNextBufferChunk() ? 1 : 100;
}

+ 113
- 0
source/modules/juce_audio_basics/sources/juce_BufferingAudioSource.h View File

@@ -0,0 +1,113 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_BUFFERINGAUDIOSOURCE_H_INCLUDED
#define JUCE_BUFFERINGAUDIOSOURCE_H_INCLUDED
#include "juce_PositionableAudioSource.h"
//==============================================================================
/**
An AudioSource which takes another source as input, and buffers it using a thread.
Create this as a wrapper around another thread, and it will read-ahead with
a background thread to smooth out playback. You can either create one of these
directly, or use it indirectly using an AudioTransportSource.
@see PositionableAudioSource, AudioTransportSource
*/
class JUCE_API BufferingAudioSource : public PositionableAudioSource,
private TimeSliceClient
{
public:
//==============================================================================
/** Creates a BufferingAudioSource.
@param source the input source to read from
@param backgroundThread a background thread that will be used for the
background read-ahead. This object must not be deleted
until after any BufferedAudioSources that are using it
have been deleted!
@param deleteSourceWhenDeleted if true, then the input source object will
be deleted when this object is deleted
@param numberOfSamplesToBuffer the size of buffer to use for reading ahead
@param numberOfChannels the number of channels that will be played
*/
BufferingAudioSource (PositionableAudioSource* source,
TimeSliceThread& backgroundThread,
bool deleteSourceWhenDeleted,
int numberOfSamplesToBuffer,
int numberOfChannels = 2);
/** Destructor.
The input source may be deleted depending on whether the deleteSourceWhenDeleted
flag was set in the constructor.
*/
~BufferingAudioSource();
//==============================================================================
/** Implementation of the AudioSource method. */
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override;
/** Implementation of the AudioSource method. */
void releaseResources() override;
/** Implementation of the AudioSource method. */
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override;
//==============================================================================
/** Implements the PositionableAudioSource method. */
void setNextReadPosition (int64 newPosition) override;
/** Implements the PositionableAudioSource method. */
int64 getNextReadPosition() const override;
/** Implements the PositionableAudioSource method. */
int64 getTotalLength() const override { return source->getTotalLength(); }
/** Implements the PositionableAudioSource method. */
bool isLooping() const override { return source->isLooping(); }
private:
//==============================================================================
OptionalScopedPointer<PositionableAudioSource> source;
TimeSliceThread& backgroundThread;
int numberOfSamplesToBuffer, numberOfChannels;
AudioSampleBuffer buffer;
CriticalSection bufferStartPosLock;
int64 volatile bufferValidStart, bufferValidEnd, nextPlayPos;
double volatile sampleRate;
bool wasSourceLooping, isPrepared;
bool readNextBufferChunk();
void readBufferSection (int64 start, int length, int bufferOffset);
int useTimeSlice();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BufferingAudioSource)
};
#endif // JUCE_BUFFERINGAUDIOSOURCE_H_INCLUDED

+ 185
- 0
source/modules/juce_audio_basics/sources/juce_ChannelRemappingAudioSource.cpp View File

@@ -0,0 +1,185 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
ChannelRemappingAudioSource::ChannelRemappingAudioSource (AudioSource* const source_,
const bool deleteSourceWhenDeleted)
: source (source_, deleteSourceWhenDeleted),
requiredNumberOfChannels (2),
buffer (2, 16)
{
remappedInfo.buffer = &buffer;
remappedInfo.startSample = 0;
}
ChannelRemappingAudioSource::~ChannelRemappingAudioSource() {}
//==============================================================================
void ChannelRemappingAudioSource::setNumberOfChannelsToProduce (const int requiredNumberOfChannels_)
{
const ScopedLock sl (lock);
requiredNumberOfChannels = requiredNumberOfChannels_;
}
void ChannelRemappingAudioSource::clearAllMappings()
{
const ScopedLock sl (lock);
remappedInputs.clear();
remappedOutputs.clear();
}
void ChannelRemappingAudioSource::setInputChannelMapping (const int destIndex, const int sourceIndex)
{
const ScopedLock sl (lock);
while (remappedInputs.size() < destIndex)
remappedInputs.add (-1);
remappedInputs.set (destIndex, sourceIndex);
}
void ChannelRemappingAudioSource::setOutputChannelMapping (const int sourceIndex, const int destIndex)
{
const ScopedLock sl (lock);
while (remappedOutputs.size() < sourceIndex)
remappedOutputs.add (-1);
remappedOutputs.set (sourceIndex, destIndex);
}
int ChannelRemappingAudioSource::getRemappedInputChannel (const int inputChannelIndex) const
{
const ScopedLock sl (lock);
if (inputChannelIndex >= 0 && inputChannelIndex < remappedInputs.size())
return remappedInputs.getUnchecked (inputChannelIndex);
return -1;
}
int ChannelRemappingAudioSource::getRemappedOutputChannel (const int outputChannelIndex) const
{
const ScopedLock sl (lock);
if (outputChannelIndex >= 0 && outputChannelIndex < remappedOutputs.size())
return remappedOutputs .getUnchecked (outputChannelIndex);
return -1;
}
//==============================================================================
void ChannelRemappingAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate)
{
source->prepareToPlay (samplesPerBlockExpected, sampleRate);
}
void ChannelRemappingAudioSource::releaseResources()
{
source->releaseResources();
}
void ChannelRemappingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill)
{
const ScopedLock sl (lock);
buffer.setSize (requiredNumberOfChannels, bufferToFill.numSamples, false, false, true);
const int numChans = bufferToFill.buffer->getNumChannels();
for (int i = 0; i < buffer.getNumChannels(); ++i)
{
const int remappedChan = getRemappedInputChannel (i);
if (remappedChan >= 0 && remappedChan < numChans)
{
buffer.copyFrom (i, 0, *bufferToFill.buffer,
remappedChan,
bufferToFill.startSample,
bufferToFill.numSamples);
}
else
{
buffer.clear (i, 0, bufferToFill.numSamples);
}
}
remappedInfo.numSamples = bufferToFill.numSamples;
source->getNextAudioBlock (remappedInfo);
bufferToFill.clearActiveBufferRegion();
for (int i = 0; i < requiredNumberOfChannels; ++i)
{
const int remappedChan = getRemappedOutputChannel (i);
if (remappedChan >= 0 && remappedChan < numChans)
{
bufferToFill.buffer->addFrom (remappedChan, bufferToFill.startSample,
buffer, i, 0, bufferToFill.numSamples);
}
}
}
//==============================================================================
XmlElement* ChannelRemappingAudioSource::createXml() const
{
XmlElement* e = new XmlElement ("MAPPINGS");
String ins, outs;
const ScopedLock sl (lock);
for (int i = 0; i < remappedInputs.size(); ++i)
ins << remappedInputs.getUnchecked(i) << ' ';
for (int i = 0; i < remappedOutputs.size(); ++i)
outs << remappedOutputs.getUnchecked(i) << ' ';
e->setAttribute ("inputs", ins.trimEnd());
e->setAttribute ("outputs", outs.trimEnd());
return e;
}
void ChannelRemappingAudioSource::restoreFromXml (const XmlElement& e)
{
if (e.hasTagName ("MAPPINGS"))
{
const ScopedLock sl (lock);
clearAllMappings();
StringArray ins, outs;
ins.addTokens (e.getStringAttribute ("inputs"), false);
outs.addTokens (e.getStringAttribute ("outputs"), false);
for (int i = 0; i < ins.size(); ++i)
remappedInputs.add (ins[i].getIntValue());
for (int i = 0; i < outs.size(); ++i)
remappedOutputs.add (outs[i].getIntValue());
}
}

+ 148
- 0
source/modules/juce_audio_basics/sources/juce_ChannelRemappingAudioSource.h View File

@@ -0,0 +1,148 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_CHANNELREMAPPINGAUDIOSOURCE_H_INCLUDED
#define JUCE_CHANNELREMAPPINGAUDIOSOURCE_H_INCLUDED
#include "juce_AudioSource.h"
//==============================================================================
/**
An AudioSource that takes the audio from another source, and re-maps its
input and output channels to a different arrangement.
You can use this to increase or decrease the number of channels that an
audio source uses, or to re-order those channels.
Call the reset() method before using it to set up a default mapping, and then
the setInputChannelMapping() and setOutputChannelMapping() methods to
create an appropriate mapping, otherwise no channels will be connected and
it'll produce silence.
@see AudioSource
*/
class ChannelRemappingAudioSource : public AudioSource
{
public:
//==============================================================================
/** Creates a remapping source that will pass on audio from the given input.
@param source the input source to use. Make sure that this doesn't
get deleted before the ChannelRemappingAudioSource object
@param deleteSourceWhenDeleted if true, the input source will be deleted
when this object is deleted, if false, the caller is
responsible for its deletion
*/
ChannelRemappingAudioSource (AudioSource* source,
bool deleteSourceWhenDeleted);
/** Destructor. */
~ChannelRemappingAudioSource();
//==============================================================================
/** Specifies a number of channels that this audio source must produce from its
getNextAudioBlock() callback.
*/
void setNumberOfChannelsToProduce (int requiredNumberOfChannels);
/** Clears any mapped channels.
After this, no channels are mapped, so this object will produce silence. Create
some mappings with setInputChannelMapping() and setOutputChannelMapping().
*/
void clearAllMappings();
/** Creates an input channel mapping.
When the getNextAudioBlock() method is called, the data in channel sourceChannelIndex of the incoming
data will be sent to destChannelIndex of our input source.
@param destChannelIndex the index of an input channel in our input audio source (i.e. the
source specified when this object was created).
@param sourceChannelIndex the index of the input channel in the incoming audio data buffer
during our getNextAudioBlock() callback
*/
void setInputChannelMapping (int destChannelIndex,
int sourceChannelIndex);
/** Creates an output channel mapping.
When the getNextAudioBlock() method is called, the data returned in channel sourceChannelIndex by
our input audio source will be copied to channel destChannelIndex of the final buffer.
@param sourceChannelIndex the index of an output channel coming from our input audio source
(i.e. the source specified when this object was created).
@param destChannelIndex the index of the output channel in the incoming audio data buffer
during our getNextAudioBlock() callback
*/
void setOutputChannelMapping (int sourceChannelIndex,
int destChannelIndex);
/** Returns the channel from our input that will be sent to channel inputChannelIndex of
our input audio source.
*/
int getRemappedInputChannel (int inputChannelIndex) const;
/** Returns the output channel to which channel outputChannelIndex of our input audio
source will be sent to.
*/
int getRemappedOutputChannel (int outputChannelIndex) const;
//==============================================================================
/** Returns an XML object to encapsulate the state of the mappings.
@see restoreFromXml
*/
XmlElement* createXml() const;
/** Restores the mappings from an XML object created by createXML().
@see createXml
*/
void restoreFromXml (const XmlElement& e);
//==============================================================================
void prepareToPlay (int samplesPerBlockExpected, double sampleRate);
void releaseResources();
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill);
private:
//==============================================================================
OptionalScopedPointer<AudioSource> source;
Array <int> remappedInputs, remappedOutputs;
int requiredNumberOfChannels;
AudioSampleBuffer buffer;
AudioSourceChannelInfo remappedInfo;
CriticalSection lock;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChannelRemappingAudioSource)
};
#endif // JUCE_CHANNELREMAPPINGAUDIOSOURCE_H_INCLUDED

+ 77
- 0
source/modules/juce_audio_basics/sources/juce_IIRFilterAudioSource.cpp View File

@@ -0,0 +1,77 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
IIRFilterAudioSource::IIRFilterAudioSource (AudioSource* const inputSource,
const bool deleteInputWhenDeleted)
: input (inputSource, deleteInputWhenDeleted)
{
jassert (inputSource != nullptr);
for (int i = 2; --i >= 0;)
iirFilters.add (new IIRFilter());
}
IIRFilterAudioSource::~IIRFilterAudioSource() {}
//==============================================================================
void IIRFilterAudioSource::setCoefficients (const IIRCoefficients& newCoefficients)
{
for (int i = iirFilters.size(); --i >= 0;)
iirFilters.getUnchecked(i)->setCoefficients (newCoefficients);
}
void IIRFilterAudioSource::makeInactive()
{
for (int i = iirFilters.size(); --i >= 0;)
iirFilters.getUnchecked(i)->makeInactive();
}
//==============================================================================
void IIRFilterAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate)
{
input->prepareToPlay (samplesPerBlockExpected, sampleRate);
for (int i = iirFilters.size(); --i >= 0;)
iirFilters.getUnchecked(i)->reset();
}
void IIRFilterAudioSource::releaseResources()
{
input->releaseResources();
}
void IIRFilterAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill)
{
input->getNextAudioBlock (bufferToFill);
const int numChannels = bufferToFill.buffer->getNumChannels();
while (numChannels > iirFilters.size())
iirFilters.add (new IIRFilter (*iirFilters.getUnchecked (0)));
for (int i = 0; i < numChannels; ++i)
iirFilters.getUnchecked(i)
->processSamples (bufferToFill.buffer->getSampleData (i, bufferToFill.startSample),
bufferToFill.numSamples);
}

+ 73
- 0
source/modules/juce_audio_basics/sources/juce_IIRFilterAudioSource.h View File

@@ -0,0 +1,73 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_IIRFILTERAUDIOSOURCE_H_INCLUDED
#define JUCE_IIRFILTERAUDIOSOURCE_H_INCLUDED
#include "juce_AudioSource.h"
#include "../effects/juce_IIRFilter.h"
//==============================================================================
/**
An AudioSource that performs an IIR filter on another source.
*/
class JUCE_API IIRFilterAudioSource : public AudioSource
{
public:
//==============================================================================
/** Creates a IIRFilterAudioSource for a given input source.
@param inputSource the input source to read from - this must not be null
@param deleteInputWhenDeleted if true, the input source will be deleted when
this object is deleted
*/
IIRFilterAudioSource (AudioSource* inputSource,
bool deleteInputWhenDeleted);
/** Destructor. */
~IIRFilterAudioSource();
//==============================================================================
/** Changes the filter to use the same parameters as the one being passed in. */
void setCoefficients (const IIRCoefficients& newCoefficients);
/** Calls IIRFilter::makeInactive() on all the filters being used internally. */
void makeInactive();
//==============================================================================
void prepareToPlay (int samplesPerBlockExpected, double sampleRate);
void releaseResources();
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill);
private:
//==============================================================================
OptionalScopedPointer<AudioSource> input;
OwnedArray <IIRFilter> iirFilters;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (IIRFilterAudioSource)
};
#endif // JUCE_IIRFILTERAUDIOSOURCE_H_INCLUDED

+ 157
- 0
source/modules/juce_audio_basics/sources/juce_MixerAudioSource.cpp View File

@@ -0,0 +1,157 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
MixerAudioSource::MixerAudioSource()
: tempBuffer (2, 0),
currentSampleRate (0.0),
bufferSizeExpected (0)
{
}
MixerAudioSource::~MixerAudioSource()
{
removeAllInputs();
}
//==============================================================================
void MixerAudioSource::addInputSource (AudioSource* input, const bool deleteWhenRemoved)
{
if (input != nullptr && ! inputs.contains (input))
{
double localRate;
int localBufferSize;
{
const ScopedLock sl (lock);
localRate = currentSampleRate;
localBufferSize = bufferSizeExpected;
}
if (localRate > 0.0)
input->prepareToPlay (localBufferSize, localRate);
const ScopedLock sl (lock);
inputsToDelete.setBit (inputs.size(), deleteWhenRemoved);
inputs.add (input);
}
}
void MixerAudioSource::removeInputSource (AudioSource* const input)
{
if (input != nullptr)
{
ScopedPointer<AudioSource> toDelete;
{
const ScopedLock sl (lock);
const int index = inputs.indexOf (input);
if (index < 0)
return;
if (inputsToDelete [index])
toDelete = input;
inputsToDelete.shiftBits (-1, index);
inputs.remove (index);
}
input->releaseResources();
}
}
void MixerAudioSource::removeAllInputs()
{
OwnedArray<AudioSource> toDelete;
{
const ScopedLock sl (lock);
for (int i = inputs.size(); --i >= 0;)
if (inputsToDelete[i])
toDelete.add (inputs.getUnchecked(i));
inputs.clear();
}
for (int i = toDelete.size(); --i >= 0;)
toDelete.getUnchecked(i)->releaseResources();
}
void MixerAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate)
{
tempBuffer.setSize (2, samplesPerBlockExpected);
const ScopedLock sl (lock);
currentSampleRate = sampleRate;
bufferSizeExpected = samplesPerBlockExpected;
for (int i = inputs.size(); --i >= 0;)
inputs.getUnchecked(i)->prepareToPlay (samplesPerBlockExpected, sampleRate);
}
void MixerAudioSource::releaseResources()
{
const ScopedLock sl (lock);
for (int i = inputs.size(); --i >= 0;)
inputs.getUnchecked(i)->releaseResources();
tempBuffer.setSize (2, 0);
currentSampleRate = 0;
bufferSizeExpected = 0;
}
void MixerAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info)
{
const ScopedLock sl (lock);
if (inputs.size() > 0)
{
inputs.getUnchecked(0)->getNextAudioBlock (info);
if (inputs.size() > 1)
{
tempBuffer.setSize (jmax (1, info.buffer->getNumChannels()),
info.buffer->getNumSamples());
AudioSourceChannelInfo info2 (&tempBuffer, 0, info.numSamples);
for (int i = 1; i < inputs.size(); ++i)
{
inputs.getUnchecked(i)->getNextAudioBlock (info2);
for (int chan = 0; chan < info.buffer->getNumChannels(); ++chan)
info.buffer->addFrom (chan, info.startSample, tempBuffer, chan, 0, info.numSamples);
}
}
}
else
{
info.clearActiveBufferRegion();
}
}

+ 103
- 0
source/modules/juce_audio_basics/sources/juce_MixerAudioSource.h View File

@@ -0,0 +1,103 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_MIXERAUDIOSOURCE_H_INCLUDED
#define JUCE_MIXERAUDIOSOURCE_H_INCLUDED
#include "juce_AudioSource.h"
//==============================================================================
/**
An AudioSource that mixes together the output of a set of other AudioSources.
Input sources can be added and removed while the mixer is running as long as their
prepareToPlay() and releaseResources() methods are called before and after adding
them to the mixer.
*/
class JUCE_API MixerAudioSource : public AudioSource
{
public:
//==============================================================================
/** Creates a MixerAudioSource. */
MixerAudioSource();
/** Destructor. */
~MixerAudioSource();
//==============================================================================
/** Adds an input source to the mixer.
If the mixer is running you'll need to make sure that the input source
is ready to play by calling its prepareToPlay() method before adding it.
If the mixer is stopped, then its input sources will be automatically
prepared when the mixer's prepareToPlay() method is called.
@param newInput the source to add to the mixer
@param deleteWhenRemoved if true, then this source will be deleted when
no longer needed by the mixer.
*/
void addInputSource (AudioSource* newInput, bool deleteWhenRemoved);
/** Removes an input source.
If the source was added by calling addInputSource() with the deleteWhenRemoved
flag set, it will be deleted by this method.
*/
void removeInputSource (AudioSource* input);
/** Removes all the input sources.
Any sources which were added by calling addInputSource() with the deleteWhenRemoved
flag set will be deleted by this method.
*/
void removeAllInputs();
//==============================================================================
/** Implementation of the AudioSource method.
This will call prepareToPlay() on all its input sources.
*/
void prepareToPlay (int samplesPerBlockExpected, double sampleRate);
/** Implementation of the AudioSource method.
This will call releaseResources() on all its input sources.
*/
void releaseResources();
/** Implementation of the AudioSource method. */
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill);
private:
//==============================================================================
Array <AudioSource*> inputs;
BigInteger inputsToDelete;
CriticalSection lock;
AudioSampleBuffer tempBuffer;
double currentSampleRate;
int bufferSizeExpected;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MixerAudioSource)
};
#endif // JUCE_MIXERAUDIOSOURCE_H_INCLUDED

+ 80
- 0
source/modules/juce_audio_basics/sources/juce_PositionableAudioSource.h View File

@@ -0,0 +1,80 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_POSITIONABLEAUDIOSOURCE_H_INCLUDED
#define JUCE_POSITIONABLEAUDIOSOURCE_H_INCLUDED
#include "juce_AudioSource.h"
//==============================================================================
/**
A type of AudioSource which can be repositioned.
The basic AudioSource just streams continuously with no idea of a current
time or length, so the PositionableAudioSource is used for a finite stream
that has a current read position.
@see AudioSource, AudioTransportSource
*/
class JUCE_API PositionableAudioSource : public AudioSource
{
protected:
//==============================================================================
/** Creates the PositionableAudioSource. */
PositionableAudioSource() noexcept {}
public:
/** Destructor */
~PositionableAudioSource() {}
//==============================================================================
/** Tells the stream to move to a new position.
Calling this indicates that the next call to AudioSource::getNextAudioBlock()
should return samples from this position.
Note that this may be called on a different thread to getNextAudioBlock(),
so the subclass should make sure it's synchronised.
*/
virtual void setNextReadPosition (int64 newPosition) = 0;
/** Returns the position from which the next block will be returned.
@see setNextReadPosition
*/
virtual int64 getNextReadPosition() const = 0;
/** Returns the total length of the stream (in samples). */
virtual int64 getTotalLength() const = 0;
/** Returns true if this source is actually playing in a loop. */
virtual bool isLooping() const = 0;
/** Tells the source whether you'd like it to play in a loop. */
virtual void setLooping (bool shouldLoop) { (void) shouldLoop; }
};
#endif // JUCE_POSITIONABLEAUDIOSOURCE_H_INCLUDED

+ 257
- 0
source/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.cpp View File

@@ -0,0 +1,257 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
ResamplingAudioSource::ResamplingAudioSource (AudioSource* const inputSource,
const bool deleteInputWhenDeleted,
const int numChannels_)
: input (inputSource, deleteInputWhenDeleted),
ratio (1.0),
lastRatio (1.0),
buffer (numChannels_, 0),
bufferPos (0),
sampsInBuffer (0),
subSampleOffset (0),
numChannels (numChannels_)
{
jassert (input != nullptr);
zeromem (coefficients, sizeof (coefficients));
}
ResamplingAudioSource::~ResamplingAudioSource() {}
void ResamplingAudioSource::setResamplingRatio (const double samplesInPerOutputSample)
{
jassert (samplesInPerOutputSample > 0);
const SpinLock::ScopedLockType sl (ratioLock);
ratio = jmax (0.0, samplesInPerOutputSample);
}
void ResamplingAudioSource::prepareToPlay (int samplesPerBlockExpected,
double sampleRate)
{
const SpinLock::ScopedLockType sl (ratioLock);
input->prepareToPlay (samplesPerBlockExpected, sampleRate);
buffer.setSize (numChannels, roundToInt (samplesPerBlockExpected * ratio) + 32);
buffer.clear();
sampsInBuffer = 0;
bufferPos = 0;
subSampleOffset = 0.0;
filterStates.calloc ((size_t) numChannels);
srcBuffers.calloc ((size_t) numChannels);
destBuffers.calloc ((size_t) numChannels);
createLowPass (ratio);
resetFilters();
}
void ResamplingAudioSource::releaseResources()
{
input->releaseResources();
buffer.setSize (numChannels, 0);
}
void ResamplingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info)
{
double localRatio;
{
const SpinLock::ScopedLockType sl (ratioLock);
localRatio = ratio;
}
if (lastRatio != localRatio)
{
createLowPass (localRatio);
lastRatio = localRatio;
}
const int sampsNeeded = roundToInt (info.numSamples * localRatio) + 2;
int bufferSize = buffer.getNumSamples();
if (bufferSize < sampsNeeded + 8)
{
bufferPos %= bufferSize;
bufferSize = sampsNeeded + 32;
buffer.setSize (buffer.getNumChannels(), bufferSize, true, true);
}
bufferPos %= bufferSize;
int endOfBufferPos = bufferPos + sampsInBuffer;
const int channelsToProcess = jmin (numChannels, info.buffer->getNumChannels());
while (sampsNeeded > sampsInBuffer)
{
endOfBufferPos %= bufferSize;
int numToDo = jmin (sampsNeeded - sampsInBuffer,
bufferSize - endOfBufferPos);
AudioSourceChannelInfo readInfo (&buffer, endOfBufferPos, numToDo);
input->getNextAudioBlock (readInfo);
if (localRatio > 1.0001)
{
// for down-sampling, pre-apply the filter..
for (int i = channelsToProcess; --i >= 0;)
applyFilter (buffer.getSampleData (i, endOfBufferPos), numToDo, filterStates[i]);
}
sampsInBuffer += numToDo;
endOfBufferPos += numToDo;
}
for (int channel = 0; channel < channelsToProcess; ++channel)
{
destBuffers[channel] = info.buffer->getSampleData (channel, info.startSample);
srcBuffers[channel] = buffer.getSampleData (channel, 0);
}
int nextPos = (bufferPos + 1) % bufferSize;
for (int m = info.numSamples; --m >= 0;)
{
const float alpha = (float) subSampleOffset;
for (int channel = 0; channel < channelsToProcess; ++channel)
*destBuffers[channel]++ = srcBuffers[channel][bufferPos]
+ alpha * (srcBuffers[channel][nextPos] - srcBuffers[channel][bufferPos]);
subSampleOffset += localRatio;
jassert (sampsInBuffer > 0);
while (subSampleOffset >= 1.0)
{
if (++bufferPos >= bufferSize)
bufferPos = 0;
--sampsInBuffer;
nextPos = (bufferPos + 1) % bufferSize;
subSampleOffset -= 1.0;
}
}
if (localRatio < 0.9999)
{
// for up-sampling, apply the filter after transposing..
for (int i = channelsToProcess; --i >= 0;)
applyFilter (info.buffer->getSampleData (i, info.startSample), info.numSamples, filterStates[i]);
}
else if (localRatio <= 1.0001 && info.numSamples > 0)
{
// if the filter's not currently being applied, keep it stoked with the last couple of samples to avoid discontinuities
for (int i = channelsToProcess; --i >= 0;)
{
const float* const endOfBuffer = info.buffer->getSampleData (i, info.startSample + info.numSamples - 1);
FilterState& fs = filterStates[i];
if (info.numSamples > 1)
{
fs.y2 = fs.x2 = *(endOfBuffer - 1);
}
else
{
fs.y2 = fs.y1;
fs.x2 = fs.x1;
}
fs.y1 = fs.x1 = *endOfBuffer;
}
}
jassert (sampsInBuffer >= 0);
}
void ResamplingAudioSource::createLowPass (const double frequencyRatio)
{
const double proportionalRate = (frequencyRatio > 1.0) ? 0.5 / frequencyRatio
: 0.5 * frequencyRatio;
const double n = 1.0 / std::tan (double_Pi * jmax (0.001, proportionalRate));
const double nSquared = n * n;
const double c1 = 1.0 / (1.0 + std::sqrt (2.0) * n + nSquared);
setFilterCoefficients (c1,
c1 * 2.0f,
c1,
1.0,
c1 * 2.0 * (1.0 - nSquared),
c1 * (1.0 - std::sqrt (2.0) * n + nSquared));
}
void ResamplingAudioSource::setFilterCoefficients (double c1, double c2, double c3, double c4, double c5, double c6)
{
const double a = 1.0 / c4;
c1 *= a;
c2 *= a;
c3 *= a;
c5 *= a;
c6 *= a;
coefficients[0] = c1;
coefficients[1] = c2;
coefficients[2] = c3;
coefficients[3] = c4;
coefficients[4] = c5;
coefficients[5] = c6;
}
void ResamplingAudioSource::resetFilters()
{
filterStates.clear ((size_t) numChannels);
}
void ResamplingAudioSource::applyFilter (float* samples, int num, FilterState& fs)
{
while (--num >= 0)
{
const double in = *samples;
double out = coefficients[0] * in
+ coefficients[1] * fs.x1
+ coefficients[2] * fs.x2
- coefficients[4] * fs.y1
- coefficients[5] * fs.y2;
#if JUCE_INTEL
if (! (out < -1.0e-8 || out > 1.0e-8))
out = 0;
#endif
fs.x2 = fs.x1;
fs.x1 = in;
fs.y2 = fs.y1;
fs.y1 = out;
*samples++ = (float) out;
}
}

+ 105
- 0
source/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.h View File

@@ -0,0 +1,105 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_RESAMPLINGAUDIOSOURCE_H_INCLUDED
#define JUCE_RESAMPLINGAUDIOSOURCE_H_INCLUDED
#include "juce_AudioSource.h"
//==============================================================================
/**
A type of AudioSource that takes an input source and changes its sample rate.
@see AudioSource
*/
class JUCE_API ResamplingAudioSource : public AudioSource
{
public:
//==============================================================================
/** Creates a ResamplingAudioSource for a given input source.
@param inputSource the input source to read from
@param deleteInputWhenDeleted if true, the input source will be deleted when
this object is deleted
@param numChannels the number of channels to process
*/
ResamplingAudioSource (AudioSource* inputSource,
bool deleteInputWhenDeleted,
int numChannels = 2);
/** Destructor. */
~ResamplingAudioSource();
/** Changes the resampling ratio.
(This value can be changed at any time, even while the source is running).
@param samplesInPerOutputSample if set to 1.0, the input is passed through; higher
values will speed it up; lower values will slow it
down. The ratio must be greater than 0
*/
void setResamplingRatio (double samplesInPerOutputSample);
/** Returns the current resampling ratio.
This is the value that was set by setResamplingRatio().
*/
double getResamplingRatio() const noexcept { return ratio; }
//==============================================================================
void prepareToPlay (int samplesPerBlockExpected, double sampleRate);
void releaseResources();
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill);
private:
//==============================================================================
OptionalScopedPointer<AudioSource> input;
double ratio, lastRatio;
AudioSampleBuffer buffer;
int bufferPos, sampsInBuffer;
double subSampleOffset;
double coefficients[6];
SpinLock ratioLock;
const int numChannels;
HeapBlock<float*> destBuffers, srcBuffers;
void setFilterCoefficients (double c1, double c2, double c3, double c4, double c5, double c6);
void createLowPass (double proportionalRate);
struct FilterState
{
double x1, x2, y1, y2;
};
HeapBlock<FilterState> filterStates;
void resetFilters();
void applyFilter (float* samples, int num, FilterState& fs);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ResamplingAudioSource)
};
#endif // JUCE_RESAMPLINGAUDIOSOURCE_H_INCLUDED

+ 80
- 0
source/modules/juce_audio_basics/sources/juce_ReverbAudioSource.cpp View File

@@ -0,0 +1,80 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
ReverbAudioSource::ReverbAudioSource (AudioSource* const inputSource, const bool deleteInputWhenDeleted)
: input (inputSource, deleteInputWhenDeleted),
bypass (false)
{
jassert (inputSource != nullptr);
}
ReverbAudioSource::~ReverbAudioSource() {}
void ReverbAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate)
{
const ScopedLock sl (lock);
input->prepareToPlay (samplesPerBlockExpected, sampleRate);
reverb.setSampleRate (sampleRate);
}
void ReverbAudioSource::releaseResources() {}
void ReverbAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill)
{
const ScopedLock sl (lock);
input->getNextAudioBlock (bufferToFill);
if (! bypass)
{
float* const firstChannel = bufferToFill.buffer->getSampleData (0, bufferToFill.startSample);
if (bufferToFill.buffer->getNumChannels() > 1)
{
reverb.processStereo (firstChannel,
bufferToFill.buffer->getSampleData (1, bufferToFill.startSample),
bufferToFill.numSamples);
}
else
{
reverb.processMono (firstChannel, bufferToFill.numSamples);
}
}
}
void ReverbAudioSource::setParameters (const Reverb::Parameters& newParams)
{
const ScopedLock sl (lock);
reverb.setParameters (newParams);
}
void ReverbAudioSource::setBypassed (bool b) noexcept
{
if (bypass != b)
{
const ScopedLock sl (lock);
bypass = b;
reverb.reset();
}
}

+ 79
- 0
source/modules/juce_audio_basics/sources/juce_ReverbAudioSource.h View File

@@ -0,0 +1,79 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_REVERBAUDIOSOURCE_H_INCLUDED
#define JUCE_REVERBAUDIOSOURCE_H_INCLUDED
#include "juce_AudioSource.h"
#include "../effects/juce_Reverb.h"
//==============================================================================
/**
An AudioSource that uses the Reverb class to apply a reverb to another AudioSource.
@see Reverb
*/
class JUCE_API ReverbAudioSource : public AudioSource
{
public:
/** Creates a ReverbAudioSource to process a given input source.
@param inputSource the input source to read from - this must not be null
@param deleteInputWhenDeleted if true, the input source will be deleted when
this object is deleted
*/
ReverbAudioSource (AudioSource* inputSource,
bool deleteInputWhenDeleted);
/** Destructor. */
~ReverbAudioSource();
//==============================================================================
/** Returns the parameters from the reverb. */
const Reverb::Parameters& getParameters() const noexcept { return reverb.getParameters(); }
/** Changes the reverb's parameters. */
void setParameters (const Reverb::Parameters& newParams);
void setBypassed (bool isBypassed) noexcept;
bool isBypassed() const noexcept { return bypass; }
//==============================================================================
void prepareToPlay (int samplesPerBlockExpected, double sampleRate);
void releaseResources();
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill);
private:
//==============================================================================
CriticalSection lock;
OptionalScopedPointer<AudioSource> input;
Reverb reverb;
volatile bool bypass;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ReverbAudioSource)
};
#endif // JUCE_REVERBAUDIOSOURCE_H_INCLUDED

+ 76
- 0
source/modules/juce_audio_basics/sources/juce_ToneGeneratorAudioSource.cpp View File

@@ -0,0 +1,76 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
ToneGeneratorAudioSource::ToneGeneratorAudioSource()
: frequency (1000.0),
sampleRate (44100.0),
currentPhase (0.0),
phasePerSample (0.0),
amplitude (0.5f)
{
}
ToneGeneratorAudioSource::~ToneGeneratorAudioSource()
{
}
//==============================================================================
void ToneGeneratorAudioSource::setAmplitude (const float newAmplitude)
{
amplitude = newAmplitude;
}
void ToneGeneratorAudioSource::setFrequency (const double newFrequencyHz)
{
frequency = newFrequencyHz;
phasePerSample = 0.0;
}
//==============================================================================
void ToneGeneratorAudioSource::prepareToPlay (int /*samplesPerBlockExpected*/,
double sampleRate_)
{
currentPhase = 0.0;
phasePerSample = 0.0;
sampleRate = sampleRate_;
}
void ToneGeneratorAudioSource::releaseResources()
{
}
void ToneGeneratorAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info)
{
if (phasePerSample == 0.0)
phasePerSample = double_Pi * 2.0 / (sampleRate / frequency);
for (int i = 0; i < info.numSamples; ++i)
{
const float sample = amplitude * (float) std::sin (currentPhase);
currentPhase += phasePerSample;
for (int j = info.buffer->getNumChannels(); --j >= 0;)
*info.buffer->getSampleData (j, info.startSample + i) = sample;
}
}

+ 75
- 0
source/modules/juce_audio_basics/sources/juce_ToneGeneratorAudioSource.h View File

@@ -0,0 +1,75 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_TONEGENERATORAUDIOSOURCE_H_INCLUDED
#define JUCE_TONEGENERATORAUDIOSOURCE_H_INCLUDED
#include "juce_AudioSource.h"
//==============================================================================
/**
A simple AudioSource that generates a sine wave.
*/
class JUCE_API ToneGeneratorAudioSource : public AudioSource
{
public:
//==============================================================================
/** Creates a ToneGeneratorAudioSource. */
ToneGeneratorAudioSource();
/** Destructor. */
~ToneGeneratorAudioSource();
//==============================================================================
/** Sets the signal's amplitude. */
void setAmplitude (float newAmplitude);
/** Sets the signal's frequency. */
void setFrequency (double newFrequencyHz);
//==============================================================================
/** Implementation of the AudioSource method. */
void prepareToPlay (int samplesPerBlockExpected, double sampleRate);
/** Implementation of the AudioSource method. */
void releaseResources();
/** Implementation of the AudioSource method. */
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill);
private:
//==============================================================================
double frequency, sampleRate;
double currentPhase, phasePerSample;
float amplitude;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToneGeneratorAudioSource)
};
#endif // JUCE_TONEGENERATORAUDIOSOURCE_H_INCLUDED

+ 432
- 0
source/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp View File

@@ -0,0 +1,432 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
SynthesiserSound::SynthesiserSound()
{
}
SynthesiserSound::~SynthesiserSound()
{
}
//==============================================================================
SynthesiserVoice::SynthesiserVoice()
: currentSampleRate (44100.0),
currentlyPlayingNote (-1),
noteOnTime (0),
keyIsDown (false),
sostenutoPedalDown (false)
{
}
SynthesiserVoice::~SynthesiserVoice()
{
}
bool SynthesiserVoice::isPlayingChannel (const int midiChannel) const
{
return currentlyPlayingSound != nullptr
&& currentlyPlayingSound->appliesToChannel (midiChannel);
}
void SynthesiserVoice::setCurrentPlaybackSampleRate (const double newRate)
{
currentSampleRate = newRate;
}
void SynthesiserVoice::clearCurrentNote()
{
currentlyPlayingNote = -1;
currentlyPlayingSound = nullptr;
}
//==============================================================================
Synthesiser::Synthesiser()
: sampleRate (0),
lastNoteOnCounter (0),
shouldStealNotes (true)
{
for (int i = 0; i < numElementsInArray (lastPitchWheelValues); ++i)
lastPitchWheelValues[i] = 0x2000;
}
Synthesiser::~Synthesiser()
{
}
//==============================================================================
SynthesiserVoice* Synthesiser::getVoice (const int index) const
{
const ScopedLock sl (lock);
return voices [index];
}
void Synthesiser::clearVoices()
{
const ScopedLock sl (lock);
voices.clear();
}
void Synthesiser::addVoice (SynthesiserVoice* const newVoice)
{
const ScopedLock sl (lock);
voices.add (newVoice);
}
void Synthesiser::removeVoice (const int index)
{
const ScopedLock sl (lock);
voices.remove (index);
}
void Synthesiser::clearSounds()
{
const ScopedLock sl (lock);
sounds.clear();
}
void Synthesiser::addSound (const SynthesiserSound::Ptr& newSound)
{
const ScopedLock sl (lock);
sounds.add (newSound);
}
void Synthesiser::removeSound (const int index)
{
const ScopedLock sl (lock);
sounds.remove (index);
}
void Synthesiser::setNoteStealingEnabled (const bool shouldStealNotes_)
{
shouldStealNotes = shouldStealNotes_;
}
//==============================================================================
void Synthesiser::setCurrentPlaybackSampleRate (const double newRate)
{
if (sampleRate != newRate)
{
const ScopedLock sl (lock);
allNotesOff (0, false);
sampleRate = newRate;
for (int i = voices.size(); --i >= 0;)
voices.getUnchecked (i)->setCurrentPlaybackSampleRate (newRate);
}
}
void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer,
const MidiBuffer& midiData,
int startSample,
int numSamples)
{
// must set the sample rate before using this!
jassert (sampleRate != 0);
const ScopedLock sl (lock);
MidiBuffer::Iterator midiIterator (midiData);
midiIterator.setNextSamplePosition (startSample);
MidiMessage m (0xf4, 0.0);
while (numSamples > 0)
{
int midiEventPos;
const bool useEvent = midiIterator.getNextEvent (m, midiEventPos)
&& midiEventPos < startSample + numSamples;
const int numThisTime = useEvent ? midiEventPos - startSample
: numSamples;
if (numThisTime > 0)
{
for (int i = voices.size(); --i >= 0;)
voices.getUnchecked (i)->renderNextBlock (outputBuffer, startSample, numThisTime);
}
if (useEvent)
handleMidiEvent (m);
startSample += numThisTime;
numSamples -= numThisTime;
}
}
void Synthesiser::handleMidiEvent (const MidiMessage& m)
{
if (m.isNoteOn())
{
noteOn (m.getChannel(),
m.getNoteNumber(),
m.getFloatVelocity());
}
else if (m.isNoteOff())
{
noteOff (m.getChannel(),
m.getNoteNumber(),
true);
}
else if (m.isAllNotesOff() || m.isAllSoundOff())
{
allNotesOff (m.getChannel(), true);
}
else if (m.isPitchWheel())
{
const int channel = m.getChannel();
const int wheelPos = m.getPitchWheelValue();
lastPitchWheelValues [channel - 1] = wheelPos;
handlePitchWheel (channel, wheelPos);
}
else if (m.isController())
{
handleController (m.getChannel(),
m.getControllerNumber(),
m.getControllerValue());
}
}
//==============================================================================
void Synthesiser::noteOn (const int midiChannel,
const int midiNoteNumber,
const float velocity)
{
const ScopedLock sl (lock);
for (int i = sounds.size(); --i >= 0;)
{
SynthesiserSound* const sound = sounds.getUnchecked(i);
if (sound->appliesToNote (midiNoteNumber)
&& sound->appliesToChannel (midiChannel))
{
// If hitting a note that's still ringing, stop it first (it could be
// still playing because of the sustain or sostenuto pedal).
for (int j = voices.size(); --j >= 0;)
{
SynthesiserVoice* const voice = voices.getUnchecked (j);
if (voice->getCurrentlyPlayingNote() == midiNoteNumber
&& voice->isPlayingChannel (midiChannel))
stopVoice (voice, true);
}
startVoice (findFreeVoice (sound, shouldStealNotes),
sound, midiChannel, midiNoteNumber, velocity);
}
}
}
void Synthesiser::startVoice (SynthesiserVoice* const voice,
SynthesiserSound* const sound,
const int midiChannel,
const int midiNoteNumber,
const float velocity)
{
if (voice != nullptr && sound != nullptr)
{
if (voice->currentlyPlayingSound != nullptr)
voice->stopNote (false);
voice->startNote (midiNoteNumber, velocity, sound,
lastPitchWheelValues [midiChannel - 1]);
voice->currentlyPlayingNote = midiNoteNumber;
voice->noteOnTime = ++lastNoteOnCounter;
voice->currentlyPlayingSound = sound;
voice->keyIsDown = true;
voice->sostenutoPedalDown = false;
}
}
void Synthesiser::stopVoice (SynthesiserVoice* voice, const bool allowTailOff)
{
jassert (voice != nullptr);
voice->stopNote (allowTailOff);
// the subclass MUST call clearCurrentNote() if it's not tailing off! RTFM for stopNote()!
jassert (allowTailOff || (voice->getCurrentlyPlayingNote() < 0 && voice->getCurrentlyPlayingSound() == 0));
}
void Synthesiser::noteOff (const int midiChannel,
const int midiNoteNumber,
const bool allowTailOff)
{
const ScopedLock sl (lock);
for (int i = voices.size(); --i >= 0;)
{
SynthesiserVoice* const voice = voices.getUnchecked (i);
if (voice->getCurrentlyPlayingNote() == midiNoteNumber)
{
if (SynthesiserSound* const sound = voice->getCurrentlyPlayingSound())
{
if (sound->appliesToNote (midiNoteNumber)
&& sound->appliesToChannel (midiChannel))
{
voice->keyIsDown = false;
if (! (sustainPedalsDown [midiChannel] || voice->sostenutoPedalDown))
stopVoice (voice, allowTailOff);
}
}
}
}
}
void Synthesiser::allNotesOff (const int midiChannel, const bool allowTailOff)
{
const ScopedLock sl (lock);
for (int i = voices.size(); --i >= 0;)
{
SynthesiserVoice* const voice = voices.getUnchecked (i);
if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))
voice->stopNote (allowTailOff);
}
sustainPedalsDown.clear();
}
void Synthesiser::handlePitchWheel (const int midiChannel, const int wheelValue)
{
const ScopedLock sl (lock);
for (int i = voices.size(); --i >= 0;)
{
SynthesiserVoice* const voice = voices.getUnchecked (i);
if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))
voice->pitchWheelMoved (wheelValue);
}
}
void Synthesiser::handleController (const int midiChannel,
const int controllerNumber,
const int controllerValue)
{
switch (controllerNumber)
{
case 0x40: handleSustainPedal (midiChannel, controllerValue >= 64); break;
case 0x42: handleSostenutoPedal (midiChannel, controllerValue >= 64); break;
case 0x43: handleSoftPedal (midiChannel, controllerValue >= 64); break;
default: break;
}
const ScopedLock sl (lock);
for (int i = voices.size(); --i >= 0;)
{
SynthesiserVoice* const voice = voices.getUnchecked (i);
if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))
voice->controllerMoved (controllerNumber, controllerValue);
}
}
void Synthesiser::handleSustainPedal (int midiChannel, bool isDown)
{
jassert (midiChannel > 0 && midiChannel <= 16);
const ScopedLock sl (lock);
if (isDown)
{
sustainPedalsDown.setBit (midiChannel);
}
else
{
for (int i = voices.size(); --i >= 0;)
{
SynthesiserVoice* const voice = voices.getUnchecked (i);
if (voice->isPlayingChannel (midiChannel) && ! voice->keyIsDown)
stopVoice (voice, true);
}
sustainPedalsDown.clearBit (midiChannel);
}
}
void Synthesiser::handleSostenutoPedal (int midiChannel, bool isDown)
{
jassert (midiChannel > 0 && midiChannel <= 16);
const ScopedLock sl (lock);
for (int i = voices.size(); --i >= 0;)
{
SynthesiserVoice* const voice = voices.getUnchecked (i);
if (voice->isPlayingChannel (midiChannel))
{
if (isDown)
voice->sostenutoPedalDown = true;
else if (voice->sostenutoPedalDown)
stopVoice (voice, true);
}
}
}
void Synthesiser::handleSoftPedal (int midiChannel, bool /*isDown*/)
{
(void) midiChannel;
jassert (midiChannel > 0 && midiChannel <= 16);
}
//==============================================================================
SynthesiserVoice* Synthesiser::findFreeVoice (SynthesiserSound* soundToPlay,
const bool stealIfNoneAvailable) const
{
const ScopedLock sl (lock);
for (int i = voices.size(); --i >= 0;)
if (voices.getUnchecked (i)->getCurrentlyPlayingNote() < 0
&& voices.getUnchecked (i)->canPlaySound (soundToPlay))
return voices.getUnchecked (i);
if (stealIfNoneAvailable)
{
// currently this just steals the one that's been playing the longest, but could be made a bit smarter..
SynthesiserVoice* oldest = nullptr;
for (int i = voices.size(); --i >= 0;)
{
SynthesiserVoice* const voice = voices.getUnchecked (i);
if (voice->canPlaySound (soundToPlay)
&& (oldest == nullptr || oldest->noteOnTime > voice->noteOnTime))
oldest = voice;
}
jassert (oldest != nullptr);
return oldest;
}
return nullptr;
}

+ 495
- 0
source/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h View File

@@ -0,0 +1,495 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_SYNTHESISER_H_INCLUDED
#define JUCE_SYNTHESISER_H_INCLUDED
#include "../buffers/juce_AudioSampleBuffer.h"
#include "../midi/juce_MidiBuffer.h"
//==============================================================================
/**
Describes one of the sounds that a Synthesiser can play.
A synthesiser can contain one or more sounds, and a sound can choose which
midi notes and channels can trigger it.
The SynthesiserSound is a passive class that just describes what the sound is -
the actual audio rendering for a sound is done by a SynthesiserVoice. This allows
more than one SynthesiserVoice to play the same sound at the same time.
@see Synthesiser, SynthesiserVoice
*/
class JUCE_API SynthesiserSound : public ReferenceCountedObject
{
protected:
//==============================================================================
SynthesiserSound();
public:
/** Destructor. */
virtual ~SynthesiserSound();
//==============================================================================
/** Returns true if this sound should be played when a given midi note is pressed.
The Synthesiser will use this information when deciding which sounds to trigger
for a given note.
*/
virtual bool appliesToNote (const int midiNoteNumber) = 0;
/** Returns true if the sound should be triggered by midi events on a given channel.
The Synthesiser will use this information when deciding which sounds to trigger
for a given note.
*/
virtual bool appliesToChannel (const int midiChannel) = 0;
/**
*/
typedef ReferenceCountedObjectPtr <SynthesiserSound> Ptr;
private:
//==============================================================================
JUCE_LEAK_DETECTOR (SynthesiserSound)
};
//==============================================================================
/**
Represents a voice that a Synthesiser can use to play a SynthesiserSound.
A voice plays a single sound at a time, and a synthesiser holds an array of
voices so that it can play polyphonically.
@see Synthesiser, SynthesiserSound
*/
class JUCE_API SynthesiserVoice
{
public:
//==============================================================================
/** Creates a voice. */
SynthesiserVoice();
/** Destructor. */
virtual ~SynthesiserVoice();
//==============================================================================
/** Returns the midi note that this voice is currently playing.
Returns a value less than 0 if no note is playing.
*/
int getCurrentlyPlayingNote() const { return currentlyPlayingNote; }
/** Returns the sound that this voice is currently playing.
Returns nullptr if it's not playing.
*/
SynthesiserSound::Ptr getCurrentlyPlayingSound() const { return currentlyPlayingSound; }
/** Must return true if this voice object is capable of playing the given sound.
If there are different classes of sound, and different classes of voice, a voice can
choose which ones it wants to take on.
A typical implementation of this method may just return true if there's only one type
of voice and sound, or it might check the type of the sound object passed-in and
see if it's one that it understands.
*/
virtual bool canPlaySound (SynthesiserSound* sound) = 0;
/** Called to start a new note.
This will be called during the rendering callback, so must be fast and thread-safe.
*/
virtual void startNote (const int midiNoteNumber,
const float velocity,
SynthesiserSound* sound,
const int currentPitchWheelPosition) = 0;
/** Called to stop a note.
This will be called during the rendering callback, so must be fast and thread-safe.
If allowTailOff is false or the voice doesn't want to tail-off, then it must stop all
sound immediately, and must call clearCurrentNote() to reset the state of this voice
and allow the synth to reassign it another sound.
If allowTailOff is true and the voice decides to do a tail-off, then it's allowed to
begin fading out its sound, and it can stop playing until it's finished. As soon as it
finishes playing (during the rendering callback), it must make sure that it calls
clearCurrentNote().
*/
virtual void stopNote (const bool allowTailOff) = 0;
/** Called to let the voice know that the pitch wheel has been moved.
This will be called during the rendering callback, so must be fast and thread-safe.
*/
virtual void pitchWheelMoved (const int newValue) = 0;
/** Called to let the voice know that a midi controller has been moved.
This will be called during the rendering callback, so must be fast and thread-safe.
*/
virtual void controllerMoved (const int controllerNumber,
const int newValue) = 0;
//==============================================================================
/** Renders the next block of data for this voice.
The output audio data must be added to the current contents of the buffer provided.
Only the region of the buffer between startSample and (startSample + numSamples)
should be altered by this method.
If the voice is currently silent, it should just return without doing anything.
If the sound that the voice is playing finishes during the course of this rendered
block, it must call clearCurrentNote(), to tell the synthesiser that it has finished.
The size of the blocks that are rendered can change each time it is called, and may
involve rendering as little as 1 sample at a time. In between rendering callbacks,
the voice's methods will be called to tell it about note and controller events.
*/
virtual void renderNextBlock (AudioSampleBuffer& outputBuffer,
int startSample,
int numSamples) = 0;
/** Returns true if the voice is currently playing a sound which is mapped to the given
midi channel.
If it's not currently playing, this will return false.
*/
bool isPlayingChannel (int midiChannel) const;
/** Changes the voice's reference sample rate.
The rate is set so that subclasses know the output rate and can set their pitch
accordingly.
This method is called by the synth, and subclasses can access the current rate with
the currentSampleRate member.
*/
void setCurrentPlaybackSampleRate (double newRate);
protected:
//==============================================================================
/** Returns the current target sample rate at which rendering is being done.
This is available for subclasses so they can pitch things correctly.
*/
double getSampleRate() const { return currentSampleRate; }
/** Resets the state of this voice after a sound has finished playing.
The subclass must call this when it finishes playing a note and becomes available
to play new ones.
It must either call it in the stopNote() method, or if the voice is tailing off,
then it should call it later during the renderNextBlock method, as soon as it
finishes its tail-off.
It can also be called at any time during the render callback if the sound happens
to have finished, e.g. if it's playing a sample and the sample finishes.
*/
void clearCurrentNote();
private:
//==============================================================================
friend class Synthesiser;
double currentSampleRate;
int currentlyPlayingNote;
uint32 noteOnTime;
SynthesiserSound::Ptr currentlyPlayingSound;
bool keyIsDown; // the voice may still be playing when the key is not down (i.e. sustain pedal)
bool sostenutoPedalDown;
JUCE_LEAK_DETECTOR (SynthesiserVoice)
};
//==============================================================================
/**
Base class for a musical device that can play sounds.
To create a synthesiser, you'll need to create a subclass of SynthesiserSound
to describe each sound available to your synth, and a subclass of SynthesiserVoice
which can play back one of these sounds.
Then you can use the addVoice() and addSound() methods to give the synthesiser a
set of sounds, and a set of voices it can use to play them. If you only give it
one voice it will be monophonic - the more voices it has, the more polyphony it'll
have available.
Then repeatedly call the renderNextBlock() method to produce the audio. Any midi
events that go in will be scanned for note on/off messages, and these are used to
start and stop the voices playing the appropriate sounds.
While it's playing, you can also cause notes to be triggered by calling the noteOn(),
noteOff() and other controller methods.
Before rendering, be sure to call the setCurrentPlaybackSampleRate() to tell it
what the target playback rate is. This value is passed on to the voices so that
they can pitch their output correctly.
*/
class JUCE_API Synthesiser
{
public:
//==============================================================================
/** Creates a new synthesiser.
You'll need to add some sounds and voices before it'll make any sound..
*/
Synthesiser();
/** Destructor. */
virtual ~Synthesiser();
//==============================================================================
/** Deletes all voices. */
void clearVoices();
/** Returns the number of voices that have been added. */
int getNumVoices() const { return voices.size(); }
/** Returns one of the voices that have been added. */
SynthesiserVoice* getVoice (int index) const;
/** Adds a new voice to the synth.
All the voices should be the same class of object and are treated equally.
The object passed in will be managed by the synthesiser, which will delete
it later on when no longer needed. The caller should not retain a pointer to the
voice.
*/
void addVoice (SynthesiserVoice* newVoice);
/** Deletes one of the voices. */
void removeVoice (int index);
//==============================================================================
/** Deletes all sounds. */
void clearSounds();
/** Returns the number of sounds that have been added to the synth. */
int getNumSounds() const { return sounds.size(); }
/** Returns one of the sounds. */
SynthesiserSound* getSound (int index) const { return sounds [index]; }
/** Adds a new sound to the synthesiser.
The object passed in is reference counted, so will be deleted when it is removed
from the synthesiser, and when no voices are still using it.
*/
void addSound (const SynthesiserSound::Ptr& newSound);
/** Removes and deletes one of the sounds. */
void removeSound (int index);
//==============================================================================
/** If set to true, then the synth will try to take over an existing voice if
it runs out and needs to play another note.
The value of this boolean is passed into findFreeVoice(), so the result will
depend on the implementation of this method.
*/
void setNoteStealingEnabled (bool shouldStealNotes);
/** Returns true if note-stealing is enabled.
@see setNoteStealingEnabled
*/
bool isNoteStealingEnabled() const { return shouldStealNotes; }
//==============================================================================
/** Triggers a note-on event.
The default method here will find all the sounds that want to be triggered by
this note/channel. For each sound, it'll try to find a free voice, and use the
voice to start playing the sound.
Subclasses might want to override this if they need a more complex algorithm.
This method will be called automatically according to the midi data passed into
renderNextBlock(), but may be called explicitly too.
The midiChannel parameter is the channel, between 1 and 16 inclusive.
*/
virtual void noteOn (int midiChannel,
int midiNoteNumber,
float velocity);
/** Triggers a note-off event.
This will turn off any voices that are playing a sound for the given note/channel.
If allowTailOff is true, the voices will be allowed to fade out the notes gracefully
(if they can do). If this is false, the notes will all be cut off immediately.
This method will be called automatically according to the midi data passed into
renderNextBlock(), but may be called explicitly too.
The midiChannel parameter is the channel, between 1 and 16 inclusive.
*/
virtual void noteOff (int midiChannel,
int midiNoteNumber,
bool allowTailOff);
/** Turns off all notes.
This will turn off any voices that are playing a sound on the given midi channel.
If midiChannel is 0 or less, then all voices will be turned off, regardless of
which channel they're playing. Otherwise it represents a valid midi channel, from
1 to 16 inclusive.
If allowTailOff is true, the voices will be allowed to fade out the notes gracefully
(if they can do). If this is false, the notes will all be cut off immediately.
This method will be called automatically according to the midi data passed into
renderNextBlock(), but may be called explicitly too.
*/
virtual void allNotesOff (int midiChannel,
bool allowTailOff);
/** Sends a pitch-wheel message.
This will send a pitch-wheel message to any voices that are playing sounds on
the given midi channel.
This method will be called automatically according to the midi data passed into
renderNextBlock(), but may be called explicitly too.
@param midiChannel the midi channel, from 1 to 16 inclusive
@param wheelValue the wheel position, from 0 to 0x3fff, as returned by MidiMessage::getPitchWheelValue()
*/
virtual void handlePitchWheel (int midiChannel,
int wheelValue);
/** Sends a midi controller message.
This will send a midi controller message to any voices that are playing sounds on
the given midi channel.
This method will be called automatically according to the midi data passed into
renderNextBlock(), but may be called explicitly too.
@param midiChannel the midi channel, from 1 to 16 inclusive
@param controllerNumber the midi controller type, as returned by MidiMessage::getControllerNumber()
@param controllerValue the midi controller value, between 0 and 127, as returned by MidiMessage::getControllerValue()
*/
virtual void handleController (int midiChannel,
int controllerNumber,
int controllerValue);
virtual void handleSustainPedal (int midiChannel, bool isDown);
virtual void handleSostenutoPedal (int midiChannel, bool isDown);
virtual void handleSoftPedal (int midiChannel, bool isDown);
//==============================================================================
/** Tells the synthesiser what the sample rate is for the audio it's being used to
render.
This value is propagated to the voices so that they can use it to render the correct
pitches.
*/
void setCurrentPlaybackSampleRate (double sampleRate);
/** Creates the next block of audio output.
This will process the next numSamples of data from all the voices, and add that output
to the audio block supplied, starting from the offset specified. Note that the
data will be added to the current contents of the buffer, so you should clear it
before calling this method if necessary.
The midi events in the inputMidi buffer are parsed for note and controller events,
and these are used to trigger the voices. Note that the startSample offset applies
both to the audio output buffer and the midi input buffer, so any midi events
with timestamps outside the specified region will be ignored.
*/
void renderNextBlock (AudioSampleBuffer& outputAudio,
const MidiBuffer& inputMidi,
int startSample,
int numSamples);
protected:
//==============================================================================
/** This is used to control access to the rendering callback and the note trigger methods. */
CriticalSection lock;
OwnedArray <SynthesiserVoice> voices;
ReferenceCountedArray <SynthesiserSound> sounds;
/** The last pitch-wheel values for each midi channel. */
int lastPitchWheelValues [16];
/** Searches through the voices to find one that's not currently playing, and which
can play the given sound.
Returns nullptr if all voices are busy and stealing isn't enabled.
This can be overridden to implement custom voice-stealing algorithms.
*/
virtual SynthesiserVoice* findFreeVoice (SynthesiserSound* soundToPlay,
const bool stealIfNoneAvailable) const;
/** Starts a specified voice playing a particular sound.
You'll probably never need to call this, it's used internally by noteOn(), but
may be needed by subclasses for custom behaviours.
*/
void startVoice (SynthesiserVoice* voice,
SynthesiserSound* sound,
int midiChannel,
int midiNoteNumber,
float velocity);
/** Can be overridden to do custom handling of incoming midi events. */
virtual void handleMidiEvent (const MidiMessage&);
private:
//==============================================================================
double sampleRate;
uint32 lastNoteOnCounter;
bool shouldStealNotes;
BigInteger sustainPedalsDown;
void stopVoice (SynthesiserVoice*, bool allowTailOff);
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE
// Note the new parameters for this method.
virtual int findFreeVoice (const bool) const { return 0; }
#endif
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Synthesiser)
};
#endif // JUCE_SYNTHESISER_H_INCLUDED

Loading…
Cancel
Save