Signed-off-by: falkTX <falktx@gmail.com>tags/v2.1-alpha1-winvst
@@ -0,0 +1,119 @@ | |||||
#!/usr/bin/make -f | |||||
# Makefile for juce_audio_basics # | |||||
# ------------------------------ # | |||||
# Created by falkTX | |||||
# | |||||
CWD=../.. | |||||
MODULENAME=juce_audio_basics | |||||
include ../Makefile.mk | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
BUILD_CXX_FLAGS += $(JUCE_AUDIO_BASICS_FLAGS) -I.. | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
ifeq ($(MACOS),true) | |||||
OBJS = $(OBJDIR)/$(MODULENAME).mm.o | |||||
OBJS_posix32 = $(OBJDIR)/$(MODULENAME).mm.posix32.o | |||||
OBJS_posix64 = $(OBJDIR)/$(MODULENAME).mm.posix64.o | |||||
else | |||||
OBJS = $(OBJDIR)/$(MODULENAME).cpp.o | |||||
OBJS_posix32 = $(OBJDIR)/$(MODULENAME).cpp.posix32.o | |||||
OBJS_posix64 = $(OBJDIR)/$(MODULENAME).cpp.posix64.o | |||||
endif | |||||
OBJS_win32 = $(OBJDIR)/$(MODULENAME).cpp.win32.o | |||||
OBJS_win64 = $(OBJDIR)/$(MODULENAME).cpp.win64.o | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
all: $(MODULEDIR)/$(MODULENAME).a | |||||
posix32: $(MODULEDIR)/$(MODULENAME).posix32.a | |||||
posix64: $(MODULEDIR)/$(MODULENAME).posix64.a | |||||
win32: $(MODULEDIR)/$(MODULENAME).win32.a | |||||
win64: $(MODULEDIR)/$(MODULENAME).win64.a | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
clean: | |||||
rm -f $(OBJDIR)/*.o $(MODULEDIR)/$(MODULENAME)*.a | |||||
debug: | |||||
$(MAKE) DEBUG=true | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
$(MODULEDIR)/$(MODULENAME).a: $(OBJS) | |||||
-@mkdir -p $(MODULEDIR) | |||||
@echo "Creating $(MODULENAME).a" | |||||
@rm -f $@ | |||||
@$(AR) crs $@ $^ | |||||
$(MODULEDIR)/$(MODULENAME).posix32.a: $(OBJS_posix32) | |||||
-@mkdir -p $(MODULEDIR) | |||||
@echo "Creating $(MODULENAME).posix32.a" | |||||
@rm -f $@ | |||||
@$(AR) crs $@ $^ | |||||
$(MODULEDIR)/$(MODULENAME).posix64.a: $(OBJS_posix64) | |||||
-@mkdir -p $(MODULEDIR) | |||||
@echo "Creating $(MODULENAME).posix64.a" | |||||
@rm -f $@ | |||||
@$(AR) crs $@ $^ | |||||
$(MODULEDIR)/$(MODULENAME).win32.a: $(OBJS_win32) | |||||
-@mkdir -p $(MODULEDIR) | |||||
@echo "Creating $(MODULENAME).win32.a" | |||||
@rm -f $@ | |||||
@$(AR) crs $@ $^ | |||||
$(MODULEDIR)/$(MODULENAME).win64.a: $(OBJS_win64) | |||||
-@mkdir -p $(MODULEDIR) | |||||
@echo "Creating $(MODULENAME).win64.a" | |||||
@rm -f $@ | |||||
@$(AR) crs $@ $^ | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
$(OBJDIR)/$(MODULENAME).cpp.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $<" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ | |||||
$(OBJDIR)/$(MODULENAME).cpp.%32.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $< (32bit)" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -c -o $@ | |||||
$(OBJDIR)/$(MODULENAME).cpp.%64.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $< (64bit)" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -c -o $@ | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
$(OBJDIR)/$(MODULENAME).mm.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $<" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) -ObjC++ -c -o $@ | |||||
$(OBJDIR)/$(MODULENAME).mm.%32.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $< (32bit)" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -ObjC++ -c -o $@ | |||||
$(OBJDIR)/$(MODULENAME).mm.%64.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $< (64bit)" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -ObjC++ -c -o $@ | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
-include $(OBJS:%.o=%.d) | |||||
-include $(OBJS_posix32:%.o=%.d) | |||||
-include $(OBJS_posix64:%.o=%.d) | |||||
-include $(OBJS_win32:%.o=%.d) | |||||
-include $(OBJS_win64:%.o=%.d) | |||||
# ---------------------------------------------------------------------------------------------------------------------------- |
@@ -0,0 +1,155 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
A subclass of AudioPlayHead can supply information about the position and | |||||
status of a moving play head during audio playback. | |||||
One of these can be supplied to an AudioProcessor object so that it can find | |||||
out about the position of the audio that it is rendering. | |||||
@see AudioProcessor::setPlayHead, AudioProcessor::getPlayHead | |||||
*/ | |||||
class JUCE_API AudioPlayHead | |||||
{ | |||||
protected: | |||||
//============================================================================== | |||||
AudioPlayHead() {} | |||||
public: | |||||
virtual ~AudioPlayHead() {} | |||||
//============================================================================== | |||||
/** Frame rate types. */ | |||||
enum FrameRateType | |||||
{ | |||||
fps23976 = 0, | |||||
fps24 = 1, | |||||
fps25 = 2, | |||||
fps2997 = 3, | |||||
fps30 = 4, | |||||
fps2997drop = 5, | |||||
fps30drop = 6, | |||||
fps60 = 7, | |||||
fps60drop = 8, | |||||
fpsUnknown = 99 | |||||
}; | |||||
//============================================================================== | |||||
/** This structure is filled-in by the AudioPlayHead::getCurrentPosition() method. | |||||
*/ | |||||
struct JUCE_API CurrentPositionInfo | |||||
{ | |||||
/** The tempo in BPM */ | |||||
double bpm; | |||||
/** Time signature numerator, e.g. the 3 of a 3/4 time sig */ | |||||
int timeSigNumerator; | |||||
/** Time signature denominator, e.g. the 4 of a 3/4 time sig */ | |||||
int timeSigDenominator; | |||||
/** The current play position, in samples from the start of the timeline. */ | |||||
int64 timeInSamples; | |||||
/** The current play position, in seconds from the start of the timeline. */ | |||||
double timeInSeconds; | |||||
/** For timecode, the position of the start of the timeline, in seconds from 00:00:00:00. */ | |||||
double editOriginTime; | |||||
/** The current play position, in pulses-per-quarter-note. */ | |||||
double ppqPosition; | |||||
/** The position of the start of the last bar, in pulses-per-quarter-note. | |||||
This is the time from the start of the timeline to the start of the current | |||||
bar, in ppq units. | |||||
Note - this value may be unavailable on some hosts, e.g. Pro-Tools. If | |||||
it's not available, the value will be 0. | |||||
*/ | |||||
double ppqPositionOfLastBarStart; | |||||
/** The video frame rate, if applicable. */ | |||||
FrameRateType frameRate; | |||||
/** True if the transport is currently playing. */ | |||||
bool isPlaying; | |||||
/** True if the transport is currently recording. | |||||
(When isRecording is true, then isPlaying will also be true). | |||||
*/ | |||||
bool isRecording; | |||||
/** The current cycle start position in pulses-per-quarter-note. | |||||
Note that not all hosts or plugin formats may provide this value. | |||||
@see isLooping | |||||
*/ | |||||
double ppqLoopStart; | |||||
/** The current cycle end position in pulses-per-quarter-note. | |||||
Note that not all hosts or plugin formats may provide this value. | |||||
@see isLooping | |||||
*/ | |||||
double ppqLoopEnd; | |||||
/** True if the transport is currently looping. */ | |||||
bool isLooping; | |||||
//============================================================================== | |||||
bool operator== (const CurrentPositionInfo& other) const noexcept; | |||||
bool operator!= (const CurrentPositionInfo& other) const noexcept; | |||||
void resetToDefault(); | |||||
}; | |||||
//============================================================================== | |||||
/** Fills-in the given structure with details about the transport's | |||||
position at the start of the current processing block. If this method returns | |||||
false then the current play head position is not available and the given | |||||
structure will be undefined. | |||||
You can ONLY call this from your processBlock() method! Calling it at other | |||||
times will produce undefined behaviour, as the host may not have any context | |||||
in which a time would make sense, and some hosts will almost certainly have | |||||
multithreading issues if it's not called on the audio thread. | |||||
*/ | |||||
virtual bool getCurrentPosition (CurrentPositionInfo& result) = 0; | |||||
/** Returns true if this object can control the transport. */ | |||||
virtual bool canControlTransport() { return false; } | |||||
/** Starts or stops the audio. */ | |||||
virtual void transportPlay (bool shouldStartPlaying) { ignoreUnused (shouldStartPlaying); } | |||||
/** Starts or stops recording the audio. */ | |||||
virtual void transportRecord (bool shouldStartRecording) { ignoreUnused (shouldStartRecording); } | |||||
/** Rewinds the audio. */ | |||||
virtual void transportRewind() {} | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,431 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
AudioChannelSet::AudioChannelSet (uint32 c) : channels (c) {} | |||||
AudioChannelSet::AudioChannelSet (const Array<ChannelType>& c) | |||||
{ | |||||
for (auto channel : c) | |||||
addChannel (channel); | |||||
} | |||||
bool AudioChannelSet::operator== (const AudioChannelSet& other) const noexcept { return channels == other.channels; } | |||||
bool AudioChannelSet::operator!= (const AudioChannelSet& other) const noexcept { return channels != other.channels; } | |||||
bool AudioChannelSet::operator< (const AudioChannelSet& other) const noexcept { return channels < other.channels; } | |||||
String AudioChannelSet::getChannelTypeName (AudioChannelSet::ChannelType type) | |||||
{ | |||||
if (type >= discreteChannel0) | |||||
return "Discrete " + String (type - discreteChannel0 + 1); | |||||
switch (type) | |||||
{ | |||||
case left: return NEEDS_TRANS("Left"); | |||||
case right: return NEEDS_TRANS("Right"); | |||||
case centre: return NEEDS_TRANS("Centre"); | |||||
case LFE: return NEEDS_TRANS("LFE"); | |||||
case leftSurround: return NEEDS_TRANS("Left Surround"); | |||||
case rightSurround: return NEEDS_TRANS("Right Surround"); | |||||
case leftCentre: return NEEDS_TRANS("Left Centre"); | |||||
case rightCentre: return NEEDS_TRANS("Right Centre"); | |||||
case centreSurround: return NEEDS_TRANS("Centre Surround"); | |||||
case leftSurroundRear: return NEEDS_TRANS("Left Surround Rear"); | |||||
case rightSurroundRear: return NEEDS_TRANS("Right Surround Rear"); | |||||
case topMiddle: return NEEDS_TRANS("Top Middle"); | |||||
case topFrontLeft: return NEEDS_TRANS("Top Front Left"); | |||||
case topFrontCentre: return NEEDS_TRANS("Top Front Centre"); | |||||
case topFrontRight: return NEEDS_TRANS("Top Front Right"); | |||||
case topRearLeft: return NEEDS_TRANS("Top Rear Left"); | |||||
case topRearCentre: return NEEDS_TRANS("Top Rear Centre"); | |||||
case topRearRight: return NEEDS_TRANS("Top Rear Right"); | |||||
case wideLeft: return NEEDS_TRANS("Wide Left"); | |||||
case wideRight: return NEEDS_TRANS("Wide Right"); | |||||
case LFE2: return NEEDS_TRANS("LFE 2"); | |||||
case leftSurroundSide: return NEEDS_TRANS("Left Surround Side"); | |||||
case rightSurroundSide: return NEEDS_TRANS("Right Surround Side"); | |||||
case ambisonicW: return NEEDS_TRANS("Ambisonic W"); | |||||
case ambisonicX: return NEEDS_TRANS("Ambisonic X"); | |||||
case ambisonicY: return NEEDS_TRANS("Ambisonic Y"); | |||||
case ambisonicZ: return NEEDS_TRANS("Ambisonic Z"); | |||||
case topSideLeft: return NEEDS_TRANS("Top Side Left"); | |||||
case topSideRight: return NEEDS_TRANS("Top Side Right"); | |||||
default: break; | |||||
} | |||||
return "Unknown"; | |||||
} | |||||
String AudioChannelSet::getAbbreviatedChannelTypeName (AudioChannelSet::ChannelType type) | |||||
{ | |||||
if (type >= discreteChannel0) | |||||
return String (type - discreteChannel0 + 1); | |||||
switch (type) | |||||
{ | |||||
case left: return "L"; | |||||
case right: return "R"; | |||||
case centre: return "C"; | |||||
case LFE: return "Lfe"; | |||||
case leftSurround: return "Ls"; | |||||
case rightSurround: return "Rs"; | |||||
case leftCentre: return "Lc"; | |||||
case rightCentre: return "Rc"; | |||||
case centreSurround: return "Cs"; | |||||
case leftSurroundRear: return "Lrs"; | |||||
case rightSurroundRear: return "Rrs"; | |||||
case topMiddle: return "Tm"; | |||||
case topFrontLeft: return "Tfl"; | |||||
case topFrontCentre: return "Tfc"; | |||||
case topFrontRight: return "Tfr"; | |||||
case topRearLeft: return "Trl"; | |||||
case topRearCentre: return "Trc"; | |||||
case topRearRight: return "Trr"; | |||||
case wideLeft: return "Wl"; | |||||
case wideRight: return "Wr"; | |||||
case LFE2: return "Lfe2"; | |||||
case leftSurroundSide: return "Lss"; | |||||
case rightSurroundSide: return "Rss"; | |||||
case ambisonicW: return "W"; | |||||
case ambisonicX: return "X"; | |||||
case ambisonicY: return "Y"; | |||||
case ambisonicZ: return "Z"; | |||||
case topSideLeft: return "Tsl"; | |||||
case topSideRight: return "Tsr"; | |||||
default: break; | |||||
} | |||||
return {}; | |||||
} | |||||
AudioChannelSet::ChannelType AudioChannelSet::getChannelTypeFromAbbreviation (const String& abbr) | |||||
{ | |||||
if (abbr.length() > 0 && (abbr[0] >= '0' && abbr[0] <= '9')) | |||||
return static_cast<AudioChannelSet::ChannelType> (static_cast<int> (discreteChannel0) | |||||
+ abbr.getIntValue() + 1); | |||||
if (abbr == "L") return left; | |||||
if (abbr == "R") return right; | |||||
if (abbr == "C") return centre; | |||||
if (abbr == "Lfe") return LFE; | |||||
if (abbr == "Ls") return leftSurround; | |||||
if (abbr == "Rs") return rightSurround; | |||||
if (abbr == "Lc") return leftCentre; | |||||
if (abbr == "Rc") return rightCentre; | |||||
if (abbr == "Cs") return centreSurround; | |||||
if (abbr == "Lrs") return leftSurroundRear; | |||||
if (abbr == "Rrs") return rightSurroundRear; | |||||
if (abbr == "Tm") return topMiddle; | |||||
if (abbr == "Tfl") return topFrontLeft; | |||||
if (abbr == "Tfc") return topFrontCentre; | |||||
if (abbr == "Tfr") return topFrontRight; | |||||
if (abbr == "Trl") return topRearLeft; | |||||
if (abbr == "Trc") return topRearCentre; | |||||
if (abbr == "Trr") return topRearRight; | |||||
if (abbr == "Wl") return wideLeft; | |||||
if (abbr == "Wr") return wideRight; | |||||
if (abbr == "Lfe2") return LFE2; | |||||
if (abbr == "Lss") return leftSurroundSide; | |||||
if (abbr == "Rss") return rightSurroundSide; | |||||
if (abbr == "W") return ambisonicW; | |||||
if (abbr == "X") return ambisonicX; | |||||
if (abbr == "Y") return ambisonicY; | |||||
if (abbr == "Z") return ambisonicZ; | |||||
if (abbr == "Tsl") return topSideLeft; | |||||
if (abbr == "Tsr") return topSideRight; | |||||
return unknown; | |||||
} | |||||
String AudioChannelSet::getSpeakerArrangementAsString() const | |||||
{ | |||||
StringArray speakerTypes; | |||||
for (auto& speaker : getChannelTypes()) | |||||
{ | |||||
auto name = getAbbreviatedChannelTypeName (speaker); | |||||
if (name.isNotEmpty()) | |||||
speakerTypes.add (name); | |||||
} | |||||
return speakerTypes.joinIntoString (" "); | |||||
} | |||||
AudioChannelSet AudioChannelSet::fromAbbreviatedString (const String& str) | |||||
{ | |||||
AudioChannelSet set; | |||||
for (auto& abbr : StringArray::fromTokens (str, true)) | |||||
{ | |||||
auto type = getChannelTypeFromAbbreviation (abbr); | |||||
if (type != unknown) | |||||
set.addChannel (type); | |||||
} | |||||
return set; | |||||
} | |||||
String AudioChannelSet::getDescription() const | |||||
{ | |||||
if (isDiscreteLayout()) return "Discrete #" + String (size()); | |||||
if (*this == disabled()) return "Disabled"; | |||||
if (*this == mono()) return "Mono"; | |||||
if (*this == stereo()) return "Stereo"; | |||||
if (*this == createLCR()) return "LCR"; | |||||
if (*this == createLRS()) return "LRS"; | |||||
if (*this == createLCRS()) return "LCRS"; | |||||
if (*this == create5point0()) return "5.0 Surround"; | |||||
if (*this == create5point1()) return "5.1 Surround"; | |||||
if (*this == create6point0()) return "6.0 Surround"; | |||||
if (*this == create6point1()) return "6.1 Surround"; | |||||
if (*this == create6point0Music()) return "6.0 (Music) Surround"; | |||||
if (*this == create6point1Music()) return "6.1 (Music) Surround"; | |||||
if (*this == create7point0()) return "7.0 Surround"; | |||||
if (*this == create7point1()) return "7.1 Surround"; | |||||
if (*this == create7point0SDDS()) return "7.0 Surround SDDS"; | |||||
if (*this == create7point1SDDS()) return "7.1 Surround SDDS"; | |||||
if (*this == create7point0point2()) return "7.0.2 Surround"; | |||||
if (*this == create7point1point2()) return "7.1.2 Surround"; | |||||
if (*this == quadraphonic()) return "Quadraphonic"; | |||||
if (*this == pentagonal()) return "Pentagonal"; | |||||
if (*this == hexagonal()) return "Hexagonal"; | |||||
if (*this == octagonal()) return "Octagonal"; | |||||
if (*this == ambisonic()) return "Ambisonic"; | |||||
return "Unknown"; | |||||
} | |||||
bool AudioChannelSet::isDiscreteLayout() const noexcept | |||||
{ | |||||
for (auto& speaker : getChannelTypes()) | |||||
if (speaker <= topSideRight) | |||||
return false; | |||||
return true; | |||||
} | |||||
int AudioChannelSet::size() const noexcept | |||||
{ | |||||
return channels.countNumberOfSetBits(); | |||||
} | |||||
AudioChannelSet::ChannelType AudioChannelSet::getTypeOfChannel (int index) const noexcept | |||||
{ | |||||
int bit = channels.findNextSetBit(0); | |||||
for (int i = 0; i < index && bit >= 0; ++i) | |||||
bit = channels.findNextSetBit (bit + 1); | |||||
return static_cast<ChannelType> (bit); | |||||
} | |||||
int AudioChannelSet::getChannelIndexForType (AudioChannelSet::ChannelType type) const noexcept | |||||
{ | |||||
int idx = 0; | |||||
for (int bit = channels.findNextSetBit (0); bit >= 0; bit = channels.findNextSetBit (bit + 1)) | |||||
{ | |||||
if (static_cast<ChannelType> (bit) == type) | |||||
return idx; | |||||
idx++; | |||||
} | |||||
return -1; | |||||
} | |||||
Array<AudioChannelSet::ChannelType> AudioChannelSet::getChannelTypes() const | |||||
{ | |||||
Array<ChannelType> result; | |||||
for (int bit = channels.findNextSetBit(0); bit >= 0; bit = channels.findNextSetBit (bit + 1)) | |||||
result.add (static_cast<ChannelType> (bit)); | |||||
return result; | |||||
} | |||||
void AudioChannelSet::addChannel (ChannelType newChannel) | |||||
{ | |||||
const int bit = static_cast<int> (newChannel); | |||||
jassert (bit >= 0 && bit < 1024); | |||||
channels.setBit (bit); | |||||
} | |||||
void AudioChannelSet::removeChannel (ChannelType newChannel) | |||||
{ | |||||
const int bit = static_cast<int> (newChannel); | |||||
jassert (bit >= 0 && bit < 1024); | |||||
channels.clearBit (bit); | |||||
} | |||||
AudioChannelSet AudioChannelSet::disabled() { return {}; } | |||||
AudioChannelSet AudioChannelSet::mono() { return AudioChannelSet (1u << centre); } | |||||
AudioChannelSet AudioChannelSet::stereo() { return AudioChannelSet ((1u << left) | (1u << right)); } | |||||
AudioChannelSet AudioChannelSet::createLCR() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre)); } | |||||
AudioChannelSet AudioChannelSet::createLRS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << surround)); } | |||||
AudioChannelSet AudioChannelSet::createLCRS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << surround)); } | |||||
AudioChannelSet AudioChannelSet::create5point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround)); } | |||||
AudioChannelSet AudioChannelSet::create5point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurround) | (1u << rightSurround)); } | |||||
AudioChannelSet AudioChannelSet::create6point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << centreSurround)); } | |||||
AudioChannelSet AudioChannelSet::create6point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurround) | (1u << rightSurround) | (1u << centreSurround)); } | |||||
AudioChannelSet AudioChannelSet::create6point0Music() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftSurroundSide) | (1u << rightSurroundSide)); } | |||||
AudioChannelSet AudioChannelSet::create6point1Music() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << LFE) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftSurroundSide) | (1u << rightSurroundSide)); } | |||||
AudioChannelSet AudioChannelSet::create7point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); } | |||||
AudioChannelSet AudioChannelSet::create7point0SDDS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftCentre) | (1u << rightCentre)); } | |||||
AudioChannelSet AudioChannelSet::create7point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); } | |||||
AudioChannelSet AudioChannelSet::create7point1SDDS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftCentre) | (1u << rightCentre)); } | |||||
AudioChannelSet AudioChannelSet::ambisonic() { return AudioChannelSet ((1u << ambisonicW) | (1u << ambisonicX) | (1u << ambisonicY) | (1u << ambisonicZ)); } | |||||
AudioChannelSet AudioChannelSet::quadraphonic() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround)); } | |||||
AudioChannelSet AudioChannelSet::pentagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); } | |||||
AudioChannelSet AudioChannelSet::hexagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << centreSurround) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); } | |||||
AudioChannelSet AudioChannelSet::octagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << centreSurround) | (1u << wideLeft) | (1u << wideRight)); } | |||||
AudioChannelSet AudioChannelSet::create7point0point2() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear) | (1u << topSideLeft) | (1u << topSideRight)); } | |||||
AudioChannelSet AudioChannelSet::create7point1point2() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear) | (1u << topSideLeft) | (1u << topSideRight)); } | |||||
AudioChannelSet AudioChannelSet::discreteChannels (int numChannels) | |||||
{ | |||||
AudioChannelSet s; | |||||
s.channels.setRange (discreteChannel0, numChannels, true); | |||||
return s; | |||||
} | |||||
AudioChannelSet AudioChannelSet::canonicalChannelSet (int numChannels) | |||||
{ | |||||
if (numChannels == 1) return AudioChannelSet::mono(); | |||||
if (numChannels == 2) return AudioChannelSet::stereo(); | |||||
if (numChannels == 3) return AudioChannelSet::createLCR(); | |||||
if (numChannels == 4) return AudioChannelSet::quadraphonic(); | |||||
if (numChannels == 5) return AudioChannelSet::create5point0(); | |||||
if (numChannels == 6) return AudioChannelSet::create5point1(); | |||||
if (numChannels == 7) return AudioChannelSet::create7point0(); | |||||
if (numChannels == 8) return AudioChannelSet::create7point1(); | |||||
return discreteChannels (numChannels); | |||||
} | |||||
AudioChannelSet AudioChannelSet::namedChannelSet (int numChannels) | |||||
{ | |||||
if (numChannels == 1) return AudioChannelSet::mono(); | |||||
if (numChannels == 2) return AudioChannelSet::stereo(); | |||||
if (numChannels == 3) return AudioChannelSet::createLCR(); | |||||
if (numChannels == 4) return AudioChannelSet::quadraphonic(); | |||||
if (numChannels == 5) return AudioChannelSet::create5point0(); | |||||
if (numChannels == 6) return AudioChannelSet::create5point1(); | |||||
if (numChannels == 7) return AudioChannelSet::create7point0(); | |||||
if (numChannels == 8) return AudioChannelSet::create7point1(); | |||||
return {}; | |||||
} | |||||
Array<AudioChannelSet> AudioChannelSet::channelSetsWithNumberOfChannels (int numChannels) | |||||
{ | |||||
Array<AudioChannelSet> retval; | |||||
if (numChannels != 0) | |||||
{ | |||||
retval.add (AudioChannelSet::discreteChannels (numChannels)); | |||||
if (numChannels == 1) | |||||
{ | |||||
retval.add (AudioChannelSet::mono()); | |||||
} | |||||
else if (numChannels == 2) | |||||
{ | |||||
retval.add (AudioChannelSet::stereo()); | |||||
} | |||||
else if (numChannels == 3) | |||||
{ | |||||
retval.add (AudioChannelSet::createLCR()); | |||||
retval.add (AudioChannelSet::createLRS()); | |||||
} | |||||
else if (numChannels == 4) | |||||
{ | |||||
retval.add (AudioChannelSet::quadraphonic()); | |||||
retval.add (AudioChannelSet::createLCRS()); | |||||
retval.add (AudioChannelSet::ambisonic()); | |||||
} | |||||
else if (numChannels == 5) | |||||
{ | |||||
retval.add (AudioChannelSet::create5point0()); | |||||
retval.add (AudioChannelSet::pentagonal()); | |||||
} | |||||
else if (numChannels == 6) | |||||
{ | |||||
retval.add (AudioChannelSet::create5point1()); | |||||
retval.add (AudioChannelSet::create6point0()); | |||||
retval.add (AudioChannelSet::create6point0Music()); | |||||
retval.add (AudioChannelSet::hexagonal()); | |||||
} | |||||
else if (numChannels == 7) | |||||
{ | |||||
retval.add (AudioChannelSet::create7point0()); | |||||
retval.add (AudioChannelSet::create7point0SDDS()); | |||||
retval.add (AudioChannelSet::create6point1()); | |||||
retval.add (AudioChannelSet::create6point1Music()); | |||||
} | |||||
else if (numChannels == 8) | |||||
{ | |||||
retval.add (AudioChannelSet::create7point1()); | |||||
retval.add (AudioChannelSet::create7point1SDDS()); | |||||
retval.add (AudioChannelSet::octagonal()); | |||||
} | |||||
} | |||||
return retval; | |||||
} | |||||
AudioChannelSet JUCE_CALLTYPE AudioChannelSet::channelSetWithChannels (const Array<ChannelType>& channelArray) | |||||
{ | |||||
AudioChannelSet set; | |||||
for (auto ch : channelArray) | |||||
{ | |||||
jassert (! set.channels[static_cast<int> (ch)]); | |||||
set.addChannel (ch); | |||||
} | |||||
return set; | |||||
} | |||||
//============================================================================== | |||||
AudioChannelSet JUCE_CALLTYPE AudioChannelSet::fromWaveChannelMask (int32 dwChannelMask) | |||||
{ | |||||
return AudioChannelSet (static_cast<uint32> ((dwChannelMask & ((1 << 18) - 1)) << 1)); | |||||
} | |||||
int32 AudioChannelSet::getWaveChannelMask() const noexcept | |||||
{ | |||||
if (channels.getHighestBit() > topRearRight) | |||||
return -1; | |||||
return (channels.toInteger() >> 1); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,408 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Represents a set of audio channel types. | |||||
For example, you might have a set of left + right channels, which is a stereo | |||||
channel set. It is a collection of values from the AudioChannelSet::ChannelType | |||||
enum, where each type may only occur once within the set. | |||||
The documentation below lists which AudioChannelSet corresponds to which native | |||||
layouts used by AAX, VST2/VST3 and CoreAudio/AU. The layout tags in CoreAudio | |||||
are particularly confusing. For example, the layout which is labeled as "7.1 SDDS" | |||||
in Logic Pro, corresponds to CoreAudio/AU's kAudioChannelLayoutTag_DTS_7_0 tag, whereas | |||||
AAX's DTS 7.1 Layout corresponds to CoreAudio/AU's | |||||
kAudioChannelLayoutTag_MPEG_7_1_A format, etc. Please do not use the CoreAudio tag | |||||
as an indication to the actual layout of the speakers. | |||||
@see Bus | |||||
*/ | |||||
class JUCE_API AudioChannelSet | |||||
{ | |||||
public: | |||||
/** Creates an empty channel set. | |||||
You can call addChannel to add channels to the set. | |||||
*/ | |||||
AudioChannelSet() noexcept {} | |||||
/** Creates a zero-channel set which can be used to indicate that a | |||||
bus is disabled. */ | |||||
static AudioChannelSet JUCE_CALLTYPE disabled(); | |||||
//============================================================================== | |||||
/** Creates a one-channel mono set (centre). | |||||
Is equivalent to: kMonoAAX (VST), AAX_eStemFormat_Mono (AAX), kAudioChannelLayoutTag_Mono (CoreAudio) | |||||
*/ | |||||
static AudioChannelSet JUCE_CALLTYPE mono(); | |||||
/** Creates a set containing a stereo set (left, right). | |||||
Is equivalent to: kStereo (VST), AAX_eStemFormat_Stereo (AAX), kAudioChannelLayoutTag_Stereo (CoreAudio) | |||||
*/ | |||||
static AudioChannelSet JUCE_CALLTYPE stereo(); | |||||
//============================================================================== | |||||
/** Creates a set containing an LCR set (left, right, centre). | |||||
Is equivalent to: k30Cine (VST), AAX_eStemFormat_LCR (AAX), kAudioChannelLayoutTag_MPEG_3_0_A (CoreAudio) | |||||
This format is referred to as "LRC" in Cubase. | |||||
This format is referred to as "LCR" in Pro Tools. | |||||
*/ | |||||
static AudioChannelSet JUCE_CALLTYPE createLCR(); | |||||
/** Creates a set containing an LRS set (left, right, surround). | |||||
Is equivalent to: k30Music (VST), n/a (AAX), kAudioChannelLayoutTag_ITU_2_1 (CoreAudio) | |||||
This format is referred to as "LRS" in Cubase. | |||||
*/ | |||||
static AudioChannelSet JUCE_CALLTYPE createLRS(); | |||||
/** Creates a set containing an LCRS set (left, right, centre, surround). | |||||
Is equivalent to: k40Cine (VST), AAX_eStemFormat_LCRS (AAX), kAudioChannelLayoutTag_MPEG_4_0_A (CoreAudio) | |||||
This format is referred to as "LCRS (Pro Logic)" in Logic Pro. | |||||
This format is referred to as "LRCS" in Cubase. | |||||
This format is referred to as "LCRS" in Pro Tools. | |||||
*/ | |||||
static AudioChannelSet JUCE_CALLTYPE createLCRS(); | |||||
//============================================================================== | |||||
/** Creates a set for a 5.0 surround setup (left, right, centre, leftSurround, rightSurround). | |||||
Is equivalent to: k50 (VST), AAX_eStemFormat_5_0 (AAX), kAudioChannelLayoutTag_MPEG_5_0_A (CoreAudio) | |||||
This format is referred to as "5.0" in Cubase. | |||||
This format is referred to as "5.0" in Pro Tools. | |||||
*/ | |||||
static AudioChannelSet JUCE_CALLTYPE create5point0(); | |||||
/** Creates a set for a 5.1 surround setup (left, right, centre, leftSurround, rightSurround, LFE). | |||||
Is equivalent to: k51 (VST), AAX_eStemFormat_5_1 (AAX), kAudioChannelLayoutTag_MPEG_5_1_A (CoreAudio) | |||||
This format is referred to as "5.1 (ITU 775)" in Logic Pro. | |||||
This format is referred to as "5.1" in Cubase. | |||||
This format is referred to as "5.1" in Pro Tools. | |||||
*/ | |||||
static AudioChannelSet JUCE_CALLTYPE create5point1(); | |||||
/** Creates a set for a 6.0 Cine surround setup (left, right, centre, leftSurround, rightSurround, centreSurround). | |||||
Is equivalent to: k60Cine (VST), AAX_eStemFormat_6_0 (AAX), kAudioChannelLayoutTag_AudioUnit_6_0 (CoreAudio) | |||||
Logic Pro incorrectly uses this for the surround format labeled "6.1 (ES/EX)". | |||||
This format is referred to as "6.0 Cine" in Cubase. | |||||
This format is referred to as "6.0" in Pro Tools. | |||||
*/ | |||||
static AudioChannelSet JUCE_CALLTYPE create6point0(); | |||||
/** Creates a set for a 6.1 Cine surround setup (left, right, centre, leftSurround, rightSurround, centreSurround, LFE). | |||||
Is equivalent to: k61Cine (VST), AAX_eStemFormat_6_1 (AAX), kAudioChannelLayoutTag_MPEG_6_1_A (CoreAudio) | |||||
This format is referred to as "6.1" in Pro Tools. | |||||
*/ | |||||
static AudioChannelSet JUCE_CALLTYPE create6point1(); | |||||
/** Creates a set for a 6.0 Music surround setup (left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide). | |||||
Is equivalent to: k60Music (VST), n/a (AAX), kAudioChannelLayoutTag_DTS_6_0_A (CoreAudio) | |||||
This format is referred to as "6.0 Music" in Cubase. | |||||
*/ | |||||
static AudioChannelSet JUCE_CALLTYPE create6point0Music(); | |||||
/** Creates a set for a 6.0 Music surround setup (left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide, LFE). | |||||
Is equivalent to: k61Music (VST), n/a (AAX), kAudioChannelLayoutTag_DTS_6_1_A (CoreAudio) | |||||
*/ | |||||
static AudioChannelSet JUCE_CALLTYPE create6point1Music(); | |||||
/** Creates a set for a DTS 7.0 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear). | |||||
Is equivalent to: k70Music (VST), AAX_eStemFormat_7_0_DTS (AAX), kAudioChannelLayoutTag_AudioUnit_7_0 (CoreAudio) | |||||
This format is referred to as "7.0" in Pro Tools. | |||||
*/ | |||||
static AudioChannelSet JUCE_CALLTYPE create7point0(); | |||||
/** Creates a set for a SDDS 7.0 surround setup (left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre). | |||||
Is equivalent to: k70Cine (VST), AAX_eStemFormat_7_0_SDDS (AAX), kAudioChannelLayoutTag_AudioUnit_7_0_Front (CoreAudio) | |||||
This format is referred to as "7.0 SDDS" in Pro Tools. | |||||
*/ | |||||
static AudioChannelSet JUCE_CALLTYPE create7point0SDDS(); | |||||
/** Creates a set for a DTS 7.1 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, LFE). | |||||
Is equivalent to: k71CineSideFill (VST), AAX_eStemFormat_7_1_DTS (AAX), kAudioChannelLayoutTag_MPEG_7_1_C/kAudioChannelLayoutTag_ITU_3_4_1 (CoreAudio) | |||||
This format is referred to as "7.1 (3/4.1)" in Logic Pro. | |||||
This format is referred to as "7.1" in Pro Tools. | |||||
*/ | |||||
static AudioChannelSet JUCE_CALLTYPE create7point1(); | |||||
/** Creates a set for a 7.1 surround setup (left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre, LFE). | |||||
Is equivalent to: k71Cine (VST), AAX_eStemFormat_7_1_SDDS (AAX), kAudioChannelLayoutTag_MPEG_7_1_A (CoreAudio) | |||||
This format is referred to as "7.1 (SDDS)" in Logic Pro. | |||||
This format is referred to as "7.1 SDDS" in Pro Tools. | |||||
*/ | |||||
static AudioChannelSet JUCE_CALLTYPE create7point1SDDS(); | |||||
/** Creates a set for Dolby Atmos 7.0.2 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topSideLeft, topSideRight). | |||||
Is equivalent to: n/a (VST), AAX_eStemFormat_7_0_2 (AAX), n/a (CoreAudio) | |||||
*/ | |||||
static AudioChannelSet JUCE_CALLTYPE create7point0point2(); | |||||
/** Creates a set for Dolby Atmos 7.1.2 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, LFE, topSideLeft, topSideRight). | |||||
Is equivalent to: k71_2 (VST), AAX_eStemFormat_7_1_2 (AAX), n/a (CoreAudio) | |||||
*/ | |||||
static AudioChannelSet JUCE_CALLTYPE create7point1point2(); | |||||
//============================================================================== | |||||
/** Creates a set for ambisonic surround setups (ambisonicW, ambisonicX, ambisonicY, ambisonicZ). | |||||
Is equivalent to: kBFormat (VST), n/a (AAX), kAudioChannelLayoutTag_Ambisonic_B_Format (CoreAudio) | |||||
*/ | |||||
static AudioChannelSet JUCE_CALLTYPE ambisonic(); | |||||
/** Creates a set for quadraphonic surround setup (left, right, leftSurround, rightSurround) | |||||
Is equivalent to: k40Music (VST), AAX_eStemFormat_Quad (AAX), kAudioChannelLayoutTag_Quadraphonic (CoreAudio) | |||||
This format is referred to as "Quadraphonic" in Logic Pro. | |||||
This format is referred to as "Quadro" in Cubase. | |||||
This format is referred to as "Quad" in Pro Tools. | |||||
*/ | |||||
static AudioChannelSet JUCE_CALLTYPE quadraphonic(); | |||||
/** Creates a set for pentagonal surround setup (left, right, centre, leftSurroundRear, rightSurroundRear). | |||||
Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Pentagonal (CoreAudio) | |||||
*/ | |||||
static AudioChannelSet JUCE_CALLTYPE pentagonal(); | |||||
/** Creates a set for hexagonal surround setup (left, right, leftSurroundRear, rightSurroundRear, centre, surroundCentre). | |||||
Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Hexagonal (CoreAudio) | |||||
*/ | |||||
static AudioChannelSet JUCE_CALLTYPE hexagonal(); | |||||
/** Creates a set for octagonal surround setup (left, right, leftSurround, rightSurround, centre, centreSurround, wideLeft, wideRight). | |||||
Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Octagonal (CoreAudio) | |||||
*/ | |||||
static AudioChannelSet JUCE_CALLTYPE octagonal(); | |||||
//============================================================================== | |||||
/** Creates a set of untyped discrete channels. */ | |||||
static AudioChannelSet JUCE_CALLTYPE discreteChannels (int numChannels); | |||||
/** Create a canonical channel set for a given number of channels. | |||||
For example, numChannels = 1 will return mono, numChannels = 2 will return stereo, etc. */ | |||||
static AudioChannelSet JUCE_CALLTYPE canonicalChannelSet (int numChannels); | |||||
/** Create a channel set for a given number of channels which is non-discrete. | |||||
If numChannels is larger than the number of channels of the surround format | |||||
with the maximum amount of channels (currently 7.1 Surround), then this | |||||
function returns an empty set.*/ | |||||
static AudioChannelSet JUCE_CALLTYPE namedChannelSet (int numChannels); | |||||
/** Return an array of channel sets which have a given number of channels */ | |||||
static Array<AudioChannelSet> JUCE_CALLTYPE channelSetsWithNumberOfChannels (int numChannels); | |||||
//============================================================================== | |||||
/** Represents different audio channel types. */ | |||||
enum ChannelType | |||||
{ | |||||
unknown = 0, | |||||
left = 1, // L | |||||
right = 2, // R | |||||
centre = 3, // C (sometimes M for mono) | |||||
LFE = 4, | |||||
leftSurround = 5, // Ls | |||||
rightSurround = 6, // Rs | |||||
leftCentre = 7, // Lc (AAX/VST), Lc used as Lss in AU for most layouts | |||||
rightCentre = 8, // Rc (AAX/VST), Rc used as Rss in AU for most layouts | |||||
centreSurround = 9, // Cs/S | |||||
surround = centreSurround, // Cs/S | |||||
leftSurroundSide = 10, // Lss (AXX), Side Left "Sl" (VST), Left Centre "LC" (AU) | |||||
rightSurroundSide = 11, // Rss (AXX), Side right "Sr" (VST), Right Centre "Rc" (AU) | |||||
topMiddle = 12, | |||||
topFrontLeft = 13, | |||||
topFrontCentre = 14, | |||||
topFrontRight = 15, | |||||
topRearLeft = 16, | |||||
topRearCentre = 17, | |||||
topRearRight = 18, | |||||
LFE2 = 19, | |||||
leftSurroundRear = 20, // Lsr (AAX), Lcs (VST), Rls (AU) | |||||
rightSurroundRear = 21, // Rsr (AAX), Rcs (VST), Rrs (AU) | |||||
wideLeft = 22, | |||||
wideRight = 23, | |||||
ambisonicW = 24, | |||||
ambisonicX = 25, | |||||
ambisonicY = 26, | |||||
ambisonicZ = 27, | |||||
// Used by Dolby Atmos 7.0.2 and 7.1.2 | |||||
topSideLeft = 28, // Lts (AAX), Tsl (VST) | |||||
topSideRight = 29, // Rts (AAX), Tsr (VST) | |||||
discreteChannel0 = 64 /**< Non-typed individual channels are indexed upwards from this value. */ | |||||
}; | |||||
/** Returns the name of a given channel type. For example, this method may return "Surround Left". */ | |||||
static String JUCE_CALLTYPE getChannelTypeName (ChannelType); | |||||
/** Returns the abbreviated name of a channel type. For example, this method may return "Ls". */ | |||||
static String JUCE_CALLTYPE getAbbreviatedChannelTypeName (ChannelType); | |||||
/** Returns the channel type from an abbreviated name. */ | |||||
static ChannelType JUCE_CALLTYPE getChannelTypeFromAbbreviation (const String& abbreviation); | |||||
//============================================================================== | |||||
enum | |||||
{ | |||||
maxChannelsOfNamedLayout = 10 | |||||
}; | |||||
/** Adds a channel to the set. */ | |||||
void addChannel (ChannelType newChannelType); | |||||
/** Removes a channel from the set. */ | |||||
void removeChannel (ChannelType newChannelType); | |||||
/** Returns the number of channels in the set. */ | |||||
int size() const noexcept; | |||||
/** Returns true if there are no channels in the set. */ | |||||
bool isDisabled() const noexcept { return size() == 0; } | |||||
/** Returns an array of all the types in this channel set. */ | |||||
Array<ChannelType> getChannelTypes() const; | |||||
/** Returns the type of one of the channels in the set, by index. */ | |||||
ChannelType getTypeOfChannel (int channelIndex) const noexcept; | |||||
/** Returns the index for a particular channel-type. | |||||
Will return -1 if the this set does not contain a channel of this type. */ | |||||
int getChannelIndexForType (ChannelType type) const noexcept; | |||||
/** Returns a string containing a whitespace-separated list of speaker types | |||||
corresponding to each channel. For example in a 5.1 arrangement, | |||||
the string may be "L R C Lfe Ls Rs". If the speaker arrangement is unknown, | |||||
the returned string will be empty.*/ | |||||
String getSpeakerArrangementAsString() const; | |||||
/** Returns an AudioChannelSet from a string returned by getSpeakerArrangementAsString | |||||
@see getSpeakerArrangementAsString */ | |||||
static AudioChannelSet fromAbbreviatedString (const String& set); | |||||
/** Returns the description of the current layout. For example, this method may return | |||||
"Quadraphonic". Note that the returned string may not be unique. */ | |||||
String getDescription() const; | |||||
/** Returns if this is a channel layout made-up of discrete channels. */ | |||||
bool isDiscreteLayout() const noexcept; | |||||
/** Intersect two channel layouts. */ | |||||
void intersect (const AudioChannelSet& other) { channels &= other.channels; } | |||||
/** Creates a channel set for a list of channel types. This function will assert | |||||
if you supply a duplicate channel. | |||||
Note that this method ignores the order in which the channels are given, i.e. | |||||
two arrays with the same elements but in a different order will still result | |||||
in the same channel set. | |||||
*/ | |||||
static AudioChannelSet JUCE_CALLTYPE channelSetWithChannels (const Array<ChannelType>&); | |||||
//============================================================================== | |||||
// Conversion between wave and juce channel layout identifiers | |||||
/** Create an AudioChannelSet from a WAVEFORMATEXTENSIBLE channelMask (typically used | |||||
in .wav files). */ | |||||
static AudioChannelSet JUCE_CALLTYPE fromWaveChannelMask (int32 dwChannelMask); | |||||
/** Returns a WAVEFORMATEXTENSIBLE channelMask representation (typically used in .wav | |||||
files) of the receiver. | |||||
Returns -1 if the receiver cannot be represented in a WAVEFORMATEXTENSIBLE channelMask | |||||
representation. | |||||
*/ | |||||
int32 getWaveChannelMask() const noexcept; | |||||
//============================================================================== | |||||
bool operator== (const AudioChannelSet&) const noexcept; | |||||
bool operator!= (const AudioChannelSet&) const noexcept; | |||||
bool operator< (const AudioChannelSet&) const noexcept; | |||||
private: | |||||
//============================================================================== | |||||
BigInteger channels; | |||||
//============================================================================== | |||||
explicit AudioChannelSet (uint32); | |||||
explicit AudioChannelSet (const Array<ChannelType>&); | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,603 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
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 auto scale = 1.0f / (float) 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 auto scale = 1.0f / (float) 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", "Audio") {} | |||||
template <class F1, class E1, class F2, class E2> | |||||
struct Test5 | |||||
{ | |||||
static void test (UnitTest& unitTest, Random& r) | |||||
{ | |||||
test (unitTest, false, r); | |||||
test (unitTest, true, r); | |||||
} | |||||
static void test (UnitTest& unitTest, bool inPlace, Random& r) | |||||
{ | |||||
const int numSamples = 2048; | |||||
int32 original [numSamples], converted [numSamples], reversed [numSamples]; | |||||
{ | |||||
AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::NonConst> d (original); | |||||
bool clippingFailed = false; | |||||
for (int i = 0; i < numSamples / 2; ++i) | |||||
{ | |||||
d.setAsFloat (r.nextFloat() * 2.2f - 1.1f); | |||||
if (! d.isFloatingPoint()) | |||||
clippingFailed = d.getAsFloat() > 1.0f || d.getAsFloat() < -1.0f || clippingFailed; | |||||
++d; | |||||
d.setAsInt32 (r.nextInt()); | |||||
++d; | |||||
} | |||||
unitTest.expect (! clippingFailed); | |||||
} | |||||
// convert data from the source to dest format.. | |||||
ScopedPointer<AudioData::Converter> conv (new AudioData::ConverterInstance <AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const>, | |||||
AudioData::Pointer<F2, E2, AudioData::NonInterleaved, AudioData::NonConst> >()); | |||||
conv->convertSamples (inPlace ? reversed : converted, original, numSamples); | |||||
// ..and back again.. | |||||
conv = new AudioData::ConverterInstance <AudioData::Pointer<F2, E2, AudioData::NonInterleaved, AudioData::Const>, | |||||
AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::NonConst> >(); | |||||
if (! inPlace) | |||||
zeromem (reversed, sizeof (reversed)); | |||||
conv->convertSamples (reversed, inPlace ? reversed : converted, numSamples); | |||||
{ | |||||
int biggestDiff = 0; | |||||
AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const> d1 (original); | |||||
AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const> d2 (reversed); | |||||
const int errorMargin = 2 * AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const>::get32BitResolution() | |||||
+ AudioData::Pointer<F2, E2, AudioData::NonInterleaved, AudioData::Const>::get32BitResolution(); | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
biggestDiff = jmax (biggestDiff, std::abs (d1.getAsInt32() - d2.getAsInt32())); | |||||
++d1; | |||||
++d2; | |||||
} | |||||
unitTest.expect (biggestDiff <= errorMargin); | |||||
} | |||||
} | |||||
}; | |||||
template <class F1, class E1, class FormatType> | |||||
struct Test3 | |||||
{ | |||||
static void test (UnitTest& unitTest, Random& r) | |||||
{ | |||||
Test5 <F1, E1, FormatType, AudioData::BigEndian>::test (unitTest, r); | |||||
Test5 <F1, E1, FormatType, AudioData::LittleEndian>::test (unitTest, r); | |||||
} | |||||
}; | |||||
template <class FormatType, class Endianness> | |||||
struct Test2 | |||||
{ | |||||
static void test (UnitTest& unitTest, Random& r) | |||||
{ | |||||
Test3 <FormatType, Endianness, AudioData::Int8>::test (unitTest, r); | |||||
Test3 <FormatType, Endianness, AudioData::UInt8>::test (unitTest, r); | |||||
Test3 <FormatType, Endianness, AudioData::Int16>::test (unitTest, r); | |||||
Test3 <FormatType, Endianness, AudioData::Int24>::test (unitTest, r); | |||||
Test3 <FormatType, Endianness, AudioData::Int32>::test (unitTest, r); | |||||
Test3 <FormatType, Endianness, AudioData::Float32>::test (unitTest, r); | |||||
} | |||||
}; | |||||
template <class FormatType> | |||||
struct Test1 | |||||
{ | |||||
static void test (UnitTest& unitTest, Random& r) | |||||
{ | |||||
Test2 <FormatType, AudioData::BigEndian>::test (unitTest, r); | |||||
Test2 <FormatType, AudioData::LittleEndian>::test (unitTest, r); | |||||
} | |||||
}; | |||||
void runTest() override | |||||
{ | |||||
Random r = getRandom(); | |||||
beginTest ("Round-trip conversion: Int8"); | |||||
Test1 <AudioData::Int8>::test (*this, r); | |||||
beginTest ("Round-trip conversion: Int16"); | |||||
Test1 <AudioData::Int16>::test (*this, r); | |||||
beginTest ("Round-trip conversion: Int24"); | |||||
Test1 <AudioData::Int24>::test (*this, r); | |||||
beginTest ("Round-trip conversion: Int32"); | |||||
Test1 <AudioData::Int32>::test (*this, r); | |||||
beginTest ("Round-trip conversion: Float32"); | |||||
Test1 <AudioData::Float32>::test (*this, r); | |||||
} | |||||
}; | |||||
static AudioConversionTests audioConversionUnitTests; | |||||
#endif | |||||
} // namespace juce |
@@ -0,0 +1,712 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
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) (*((uint8*) 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) (((uint8) (*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) (((unsigned int) ByteOrder::littleEndian24Bit (data)) << 8); } | |||||
inline int32 getAsInt32BE() const noexcept { return (int32) (((unsigned int) 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) (int32) (maxValue * jlimit (-1.0, 1.0, (double) newValue))); } | |||||
inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) (int32) (maxValue * jlimit (-1.0, 1.0, (double) newValue))); } | |||||
inline int32 getAsInt32LE() const noexcept { return (int32) ByteOrder::swapIfBigEndian (*data); } | |||||
inline int32 getAsInt32BE() const noexcept { return (int32) ByteOrder::swapIfLittleEndian (*data); } | |||||
inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) newValue); } | |||||
inline void setAsInt32BE (int32 newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) newValue); } | |||||
inline void clear() noexcept { *data = 0; } | |||||
inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} | |||||
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } | |||||
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } | |||||
inline void copyFromSameType (Int32& source) noexcept { *data = *source.data; } | |||||
uint32* data; | |||||
enum { bytesPerSample = 4, maxValue = 0x7fffffff, resolution = 1, isFloat = 0 }; | |||||
}; | |||||
/** A 32-bit integer type, of which only the bottom 24 bits are used. */ | |||||
class Int24in32 : public Int32 | |||||
{ | |||||
public: | |||||
inline Int24in32 (void* d) noexcept : Int32 (d) {} | |||||
inline float getAsFloatLE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int32) ByteOrder::swapIfBigEndian (*data)); } | |||||
inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int32) ByteOrder::swapIfLittleEndian (*data)); } | |||||
inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) (maxValue * jlimit (-1.0, 1.0, (double) newValue))); } | |||||
inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) (maxValue * jlimit (-1.0, 1.0, (double) newValue))); } | |||||
inline int32 getAsInt32LE() const noexcept { return (int32) ByteOrder::swapIfBigEndian (*data) << 8; } | |||||
inline int32 getAsInt32BE() const noexcept { return (int32) ByteOrder::swapIfLittleEndian (*data) << 8; } | |||||
inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) newValue >> 8); } | |||||
inline void setAsInt32BE (int32 newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) newValue >> 8); } | |||||
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } | |||||
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } | |||||
inline void copyFromSameType (Int24in32& source) noexcept { *data = *source.data; } | |||||
enum { bytesPerSample = 4, maxValue = 0x7fffff, resolution = (1 << 8), isFloat = 0 }; | |||||
}; | |||||
class Float32 | |||||
{ | |||||
public: | |||||
inline Float32 (void* d) noexcept : data (static_cast<float*> (d)) {} | |||||
inline void advance() noexcept { ++data; } | |||||
inline void skip (int numSamples) noexcept { data += numSamples; } | |||||
#if JUCE_BIG_ENDIAN | |||||
inline float getAsFloatBE() const noexcept { return *data; } | |||||
inline void setAsFloatBE (float newValue) noexcept { *data = newValue; } | |||||
inline float getAsFloatLE() const noexcept { union { uint32 asInt; float asFloat; } n; n.asInt = ByteOrder::swap (*(uint32*) data); return n.asFloat; } | |||||
inline void setAsFloatLE (float newValue) noexcept { union { uint32 asInt; float asFloat; } n; n.asFloat = newValue; *(uint32*) data = ByteOrder::swap (n.asInt); } | |||||
#else | |||||
inline float getAsFloatLE() const noexcept { return *data; } | |||||
inline void setAsFloatLE (float newValue) noexcept { *data = newValue; } | |||||
inline float getAsFloatBE() const noexcept { union { uint32 asInt; float asFloat; } n; n.asInt = ByteOrder::swap (*(uint32*) data); return n.asFloat; } | |||||
inline void setAsFloatBE (float newValue) noexcept { union { uint32 asInt; float asFloat; } n; n.asFloat = newValue; *(uint32*) data = ByteOrder::swap (n.asInt); } | |||||
#endif | |||||
inline int32 getAsInt32LE() const noexcept { return (int32) roundToInt (jlimit (-1.0, 1.0, (double) getAsFloatLE()) * (double) maxValue); } | |||||
inline int32 getAsInt32BE() const noexcept { return (int32) roundToInt (jlimit (-1.0, 1.0, (double) getAsFloatBE()) * (double) maxValue); } | |||||
inline void setAsInt32LE (int32 newValue) noexcept { setAsFloatLE ((float) (newValue * (1.0 / (1.0 + maxValue)))); } | |||||
inline void setAsInt32BE (int32 newValue) noexcept { setAsFloatBE ((float) (newValue * (1.0 / (1.0 + maxValue)))); } | |||||
inline void clear() noexcept { *data = 0; } | |||||
inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} | |||||
template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsFloatLE (source.getAsFloat()); } | |||||
template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsFloatBE (source.getAsFloat()); } | |||||
inline void copyFromSameType (Float32& source) noexcept { *data = *source.data; } | |||||
float* data; | |||||
enum { bytesPerSample = 4, maxValue = 0x7fffffff, resolution = (1 << 8), isFloat = 1 }; | |||||
}; | |||||
//============================================================================== | |||||
class NonInterleaved | |||||
{ | |||||
public: | |||||
inline NonInterleaved() noexcept {} | |||||
inline NonInterleaved (const NonInterleaved&) noexcept {} | |||||
inline NonInterleaved (const int) noexcept {} | |||||
inline void copyFrom (const NonInterleaved&) noexcept {} | |||||
template <class SampleFormatType> inline void advanceData (SampleFormatType& s) noexcept { s.advance(); } | |||||
template <class SampleFormatType> inline void advanceDataBy (SampleFormatType& s, int numSamples) noexcept { s.skip (numSamples); } | |||||
template <class SampleFormatType> inline void clear (SampleFormatType& s, int numSamples) noexcept { s.clearMultiple (numSamples); } | |||||
template <class SampleFormatType> inline static int getNumBytesBetweenSamples (const SampleFormatType&) noexcept { return SampleFormatType::bytesPerSample; } | |||||
enum { isInterleavedType = 0, numInterleavedChannels = 1 }; | |||||
}; | |||||
class Interleaved | |||||
{ | |||||
public: | |||||
inline Interleaved() noexcept : numInterleavedChannels (1) {} | |||||
inline Interleaved (const Interleaved& other) noexcept : numInterleavedChannels (other.numInterleavedChannels) {} | |||||
inline Interleaved (const int numInterleavedChans) noexcept : numInterleavedChannels (numInterleavedChans) {} | |||||
inline void copyFrom (const Interleaved& other) noexcept { numInterleavedChannels = other.numInterleavedChannels; } | |||||
template <class SampleFormatType> inline void advanceData (SampleFormatType& s) noexcept { s.skip (numInterleavedChannels); } | |||||
template <class SampleFormatType> inline void advanceDataBy (SampleFormatType& s, int numSamples) noexcept { s.skip (numInterleavedChannels * numSamples); } | |||||
template <class SampleFormatType> inline void clear (SampleFormatType& s, int numSamples) noexcept { while (--numSamples >= 0) { s.clear(); s.skip (numInterleavedChannels); } } | |||||
template <class SampleFormatType> inline int getNumBytesBetweenSamples (const SampleFormatType&) const noexcept { return numInterleavedChannels * SampleFormatType::bytesPerSample; } | |||||
int numInterleavedChannels; | |||||
enum { isInterleavedType = 1 }; | |||||
}; | |||||
//============================================================================== | |||||
class NonConst | |||||
{ | |||||
public: | |||||
typedef void VoidType; | |||||
static inline void* toVoidPtr (VoidType* v) noexcept { return v; } | |||||
enum { isConst = 0 }; | |||||
}; | |||||
class Const | |||||
{ | |||||
public: | |||||
typedef const void VoidType; | |||||
static inline void* toVoidPtr (VoidType* v) noexcept { return const_cast<void*> (v); } | |||||
enum { isConst = 1 }; | |||||
}; | |||||
#endif | |||||
//============================================================================== | |||||
/** | |||||
A pointer to a block of audio data with a particular encoding. | |||||
This object can be used to read and write from blocks of encoded audio samples. To create one, you specify | |||||
the audio format as a series of template parameters, e.g. | |||||
@code | |||||
// this creates a pointer for reading from a const array of 16-bit little-endian packed samples. | |||||
AudioData::Pointer <AudioData::Int16, | |||||
AudioData::LittleEndian, | |||||
AudioData::NonInterleaved, | |||||
AudioData::Const> pointer (someRawAudioData); | |||||
// These methods read the sample that is being pointed to | |||||
float firstSampleAsFloat = pointer.getAsFloat(); | |||||
int32 firstSampleAsInt = pointer.getAsInt32(); | |||||
++pointer; // moves the pointer to the next sample. | |||||
pointer += 3; // skips the next 3 samples. | |||||
@endcode | |||||
The convertSamples() method lets you copy a range of samples from one format to another, automatically | |||||
converting its format. | |||||
@see AudioData::Converter | |||||
*/ | |||||
template <typename SampleFormat, | |||||
typename Endianness, | |||||
typename InterleavingType, | |||||
typename Constness> | |||||
class Pointer : private InterleavingType // (inherited for EBCO) | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a non-interleaved pointer from some raw data in the appropriate format. | |||||
This constructor is only used if you've specified the AudioData::NonInterleaved option - | |||||
for interleaved formats, use the constructor that also takes a number of channels. | |||||
*/ | |||||
Pointer (typename Constness::VoidType* sourceData) noexcept | |||||
: data (Constness::toVoidPtr (sourceData)) | |||||
{ | |||||
// If you're using interleaved data, call the other constructor! If you're using non-interleaved data, | |||||
// you should pass NonInterleaved as the template parameter for the interleaving type! | |||||
static_assert (InterleavingType::isInterleavedType == 0, "Incorrect constructor for interleaved data"); | |||||
} | |||||
/** 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 | |||||
{ | |||||
// trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead! | |||||
static_assert (Constness::isConst == 0, "Attempt to write to a const pointer"); | |||||
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 | |||||
{ | |||||
// trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead! | |||||
static_assert (Constness::isConst == 0, "Attempt to write to a const pointer"); | |||||
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 | |||||
{ | |||||
// trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead! | |||||
static_assert (Constness::isConst == 0, "Attempt to write to a const pointer"); | |||||
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 | |||||
{ | |||||
// trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead! | |||||
static_assert (Constness::isConst == 0, "Attempt to write to a const pointer"); | |||||
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 */ | |||||
Range<float> findMinAndMax (size_t numSamples) const noexcept | |||||
{ | |||||
if (numSamples == 0) | |||||
return Range<float>(); | |||||
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; | |||||
} | |||||
return Range<float> (mn, mx); | |||||
} | |||||
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; | |||||
} | |||||
return Range<float> (mn * (float) (1.0 / (1.0 + Int32::maxValue)), | |||||
mx * (float) (1.0 / (1.0 + Int32::maxValue))); | |||||
} | |||||
/** Scans a block of data, returning the lowest and highest levels as floats */ | |||||
void findMinAndMax (size_t numSamples, float& minValue, float& maxValue) const noexcept | |||||
{ | |||||
Range<float> r (findMinAndMax (numSamples)); | |||||
minValue = r.getStart(); | |||||
maxValue = r.getEnd(); | |||||
} | |||||
/** Returns true if the pointer is using a floating-point format. */ | |||||
static bool isFloatingPoint() noexcept { return (bool) SampleFormat::isFloat; } | |||||
/** Returns true if the format is big-endian. */ | |||||
static bool isBigEndian() noexcept { return (bool) Endianness::isBigEndian; } | |||||
/** Returns the number of bytes in each sample (ignoring the number of interleaved channels). */ | |||||
static int getBytesPerSample() noexcept { return (int) SampleFormat::bytesPerSample; } | |||||
/** Returns the number of interleaved channels in the format. */ | |||||
int getNumInterleavedChannels() const noexcept { return (int) this->numInterleavedChannels; } | |||||
/** Returns the number of bytes between the start address of each sample. */ | |||||
int getNumBytesBetweenSamples() const noexcept { return InterleavingType::getNumBytesBetweenSamples (data); } | |||||
/** Returns the accuracy of this format when represented as a 32-bit integer. | |||||
This is the smallest number above 0 that can be represented in the sample format, converted to | |||||
a 32-bit range. E,g. if the format is 8-bit, its resolution is 0x01000000; if the format is 24-bit, | |||||
its resolution is 0x100. | |||||
*/ | |||||
static int get32BitResolution() noexcept { return (int) SampleFormat::resolution; } | |||||
/** Returns a pointer to the underlying data. */ | |||||
const void* getRawData() const noexcept { return data.data; } | |||||
private: | |||||
//============================================================================== | |||||
SampleFormat data; | |||||
inline void advance() noexcept { this->advanceData (data); } | |||||
Pointer operator++ (int); // private to force you to use the more efficient pre-increment! | |||||
Pointer operator-- (int); | |||||
}; | |||||
//============================================================================== | |||||
/** A base class for objects that are used to convert between two different sample formats. | |||||
The AudioData::ConverterInstance implements this base class and can be templated, so | |||||
you can create an instance that converts between two particular formats, and then | |||||
store this in the abstract base class. | |||||
@see AudioData::ConverterInstance | |||||
*/ | |||||
class Converter | |||||
{ | |||||
public: | |||||
virtual ~Converter() {} | |||||
/** Converts a sequence of samples from the converter's source format into the dest format. */ | |||||
virtual void convertSamples (void* destSamples, const void* sourceSamples, int numSamples) const = 0; | |||||
/** Converts a sequence of samples from the converter's source format into the dest format. | |||||
This method takes sub-channel indexes, which can be used with interleaved formats in order to choose a | |||||
particular sub-channel of the data to be used. | |||||
*/ | |||||
virtual void convertSamples (void* destSamples, int destSubChannel, | |||||
const void* sourceSamples, int sourceSubChannel, int numSamples) const = 0; | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
A class that converts between two templated AudioData::Pointer types, and which | |||||
implements the AudioData::Converter interface. | |||||
This can be used as a concrete instance of the AudioData::Converter abstract class. | |||||
@see AudioData::Converter | |||||
*/ | |||||
template <class SourceSampleType, class DestSampleType> | |||||
class ConverterInstance : public Converter | |||||
{ | |||||
public: | |||||
ConverterInstance (int numSourceChannels = 1, int numDestChannels = 1) | |||||
: sourceChannels (numSourceChannels), destChannels (numDestChannels) | |||||
{} | |||||
void convertSamples (void* dest, const void* source, int numSamples) const override | |||||
{ | |||||
SourceSampleType s (source, sourceChannels); | |||||
DestSampleType d (dest, destChannels); | |||||
d.convertSamples (s, numSamples); | |||||
} | |||||
void convertSamples (void* dest, int destSubChannel, | |||||
const void* source, int sourceSubChannel, int numSamples) const override | |||||
{ | |||||
jassert (destSubChannel < destChannels && sourceSubChannel < sourceChannels); | |||||
SourceSampleType s (addBytesToPointer (source, sourceSubChannel * SourceSampleType::getBytesPerSample()), sourceChannels); | |||||
DestSampleType d (addBytesToPointer (dest, destSubChannel * DestSampleType::getBytesPerSample()), destChannels); | |||||
d.convertSamples (s, numSamples); | |||||
} | |||||
private: | |||||
JUCE_DECLARE_NON_COPYABLE (ConverterInstance) | |||||
const int sourceChannels, destChannels; | |||||
}; | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
A set of routines to convert buffers of 32-bit floating point data to and from | |||||
various integer formats. | |||||
Note that these functions are deprecated - the AudioData class provides a much more | |||||
flexible set of conversion classes now. | |||||
*/ | |||||
class JUCE_API AudioDataConverters | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
static void convertFloatToInt16LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 2); | |||||
static void convertFloatToInt16BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 2); | |||||
static void convertFloatToInt24LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 3); | |||||
static void convertFloatToInt24BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 3); | |||||
static void convertFloatToInt32LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); | |||||
static void convertFloatToInt32BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); | |||||
static void convertFloatToFloat32LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); | |||||
static void convertFloatToFloat32BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); | |||||
//============================================================================== | |||||
static void convertInt16LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 2); | |||||
static void convertInt16BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 2); | |||||
static void convertInt24LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 3); | |||||
static void convertInt24BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 3); | |||||
static void convertInt32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); | |||||
static void convertInt32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); | |||||
static void convertFloat32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); | |||||
static void convertFloat32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); | |||||
//============================================================================== | |||||
enum DataFormat | |||||
{ | |||||
int16LE, | |||||
int16BE, | |||||
int24LE, | |||||
int24BE, | |||||
int32LE, | |||||
int32BE, | |||||
float32LE, | |||||
float32BE, | |||||
}; | |||||
static void convertFloatToFormat (DataFormat destFormat, | |||||
const float* source, void* dest, int numSamples); | |||||
static void convertFormatToFloat (DataFormat sourceFormat, | |||||
const void* source, float* dest, int numSamples); | |||||
//============================================================================== | |||||
static void interleaveSamples (const float** source, float* dest, | |||||
int numSamples, int numChannels); | |||||
static void deinterleaveSamples (const float* source, float** dest, | |||||
int numSamples, int numChannels); | |||||
private: | |||||
AudioDataConverters(); | |||||
JUCE_DECLARE_NON_COPYABLE (AudioDataConverters) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,254 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
#if JUCE_INTEL | |||||
#define JUCE_SNAP_TO_ZERO(n) if (! (n < -1.0e-8f || n > 1.0e-8f)) n = 0; | |||||
#else | |||||
#define JUCE_SNAP_TO_ZERO(n) ignoreUnused (n) | |||||
#endif | |||||
//============================================================================== | |||||
/** | |||||
A collection of simple vector operations on arrays of floats, accelerated with | |||||
SIMD instructions where possible. | |||||
*/ | |||||
class JUCE_API FloatVectorOperations | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Clears a vector of floats. */ | |||||
static void JUCE_CALLTYPE clear (float* dest, int numValues) noexcept; | |||||
/** Clears a vector of doubles. */ | |||||
static void JUCE_CALLTYPE clear (double* dest, int numValues) noexcept; | |||||
/** Copies a repeated value into a vector of floats. */ | |||||
static void JUCE_CALLTYPE fill (float* dest, float valueToFill, int numValues) noexcept; | |||||
/** Copies a repeated value into a vector of doubles. */ | |||||
static void JUCE_CALLTYPE fill (double* dest, double valueToFill, int numValues) noexcept; | |||||
/** Copies a vector of floats. */ | |||||
static void JUCE_CALLTYPE copy (float* dest, const float* src, int numValues) noexcept; | |||||
/** Copies a vector of doubles. */ | |||||
static void JUCE_CALLTYPE copy (double* dest, const double* src, int numValues) noexcept; | |||||
/** Copies a vector of floats, multiplying each value by a given multiplier */ | |||||
static void JUCE_CALLTYPE copyWithMultiply (float* dest, const float* src, float multiplier, int numValues) noexcept; | |||||
/** Copies a vector of doubles, multiplying each value by a given multiplier */ | |||||
static void JUCE_CALLTYPE copyWithMultiply (double* dest, const double* src, double multiplier, int numValues) noexcept; | |||||
/** Adds a fixed value to the destination values. */ | |||||
static void JUCE_CALLTYPE add (float* dest, float amountToAdd, int numValues) noexcept; | |||||
/** Adds a fixed value to the destination values. */ | |||||
static void JUCE_CALLTYPE add (double* dest, double amountToAdd, int numValues) noexcept; | |||||
/** Adds a fixed value to each source value and stores it in the destination array. */ | |||||
static void JUCE_CALLTYPE add (float* dest, const float* src, float amount, int numValues) noexcept; | |||||
/** Adds a fixed value to each source value and stores it in the destination array. */ | |||||
static void JUCE_CALLTYPE add (double* dest, const double* src, double amount, int numValues) noexcept; | |||||
/** Adds the source values to the destination values. */ | |||||
static void JUCE_CALLTYPE add (float* dest, const float* src, int numValues) noexcept; | |||||
/** Adds the source values to the destination values. */ | |||||
static void JUCE_CALLTYPE add (double* dest, const double* src, int numValues) noexcept; | |||||
/** Adds each source1 value to the corresponding source2 value and stores the result in the destination array. */ | |||||
static void JUCE_CALLTYPE add (float* dest, const float* src1, const float* src2, int num) noexcept; | |||||
/** Adds each source1 value to the corresponding source2 value and stores the result in the destination array. */ | |||||
static void JUCE_CALLTYPE add (double* dest, const double* src1, const double* src2, int num) noexcept; | |||||
/** Subtracts the source values from the destination values. */ | |||||
static void JUCE_CALLTYPE subtract (float* dest, const float* src, int numValues) noexcept; | |||||
/** Subtracts the source values from the destination values. */ | |||||
static void JUCE_CALLTYPE subtract (double* dest, const double* src, int numValues) noexcept; | |||||
/** Subtracts each source2 value from the corresponding source1 value and stores the result in the destination array. */ | |||||
static void JUCE_CALLTYPE subtract (float* dest, const float* src1, const float* src2, int num) noexcept; | |||||
/** Subtracts each source2 value from the corresponding source1 value and stores the result in the destination array. */ | |||||
static void JUCE_CALLTYPE subtract (double* dest, const double* src1, const double* src2, int num) noexcept; | |||||
/** Multiplies each source value by the given multiplier, then adds it to the destination value. */ | |||||
static void JUCE_CALLTYPE addWithMultiply (float* dest, const float* src, float multiplier, int numValues) noexcept; | |||||
/** Multiplies each source value by the given multiplier, then adds it to the destination value. */ | |||||
static void JUCE_CALLTYPE addWithMultiply (double* dest, const double* src, double multiplier, int numValues) noexcept; | |||||
/** Multiplies each source1 value by the corresponding source2 value, then adds it to the destination value. */ | |||||
static void JUCE_CALLTYPE addWithMultiply (float* dest, const float* src1, const float* src2, int num) noexcept; | |||||
/** Multiplies each source1 value by the corresponding source2 value, then adds it to the destination value. */ | |||||
static void JUCE_CALLTYPE addWithMultiply (double* dest, const double* src1, const double* src2, int num) noexcept; | |||||
/** Multiplies each source value by the given multiplier, then subtracts it to the destination value. */ | |||||
static void JUCE_CALLTYPE subtractWithMultiply (float* dest, const float* src, float multiplier, int numValues) noexcept; | |||||
/** Multiplies each source value by the given multiplier, then subtracts it to the destination value. */ | |||||
static void JUCE_CALLTYPE subtractWithMultiply (double* dest, const double* src, double multiplier, int numValues) noexcept; | |||||
/** Multiplies each source1 value by the corresponding source2 value, then subtracts it to the destination value. */ | |||||
static void JUCE_CALLTYPE subtractWithMultiply (float* dest, const float* src1, const float* src2, int num) noexcept; | |||||
/** Multiplies each source1 value by the corresponding source2 value, then subtracts it to the destination value. */ | |||||
static void JUCE_CALLTYPE subtractWithMultiply (double* dest, const double* src1, const double* src2, int num) noexcept; | |||||
/** Multiplies the destination values by the source values. */ | |||||
static void JUCE_CALLTYPE multiply (float* dest, const float* src, int numValues) noexcept; | |||||
/** Multiplies the destination values by the source values. */ | |||||
static void JUCE_CALLTYPE multiply (double* dest, const double* src, int numValues) noexcept; | |||||
/** Multiplies each source1 value by the correspinding source2 value, then stores it in the destination array. */ | |||||
static void JUCE_CALLTYPE multiply (float* dest, const float* src1, const float* src2, int numValues) noexcept; | |||||
/** Multiplies each source1 value by the correspinding source2 value, then stores it in the destination array. */ | |||||
static void JUCE_CALLTYPE multiply (double* dest, const double* src1, const double* src2, int numValues) noexcept; | |||||
/** Multiplies each of the destination values by a fixed multiplier. */ | |||||
static void JUCE_CALLTYPE multiply (float* dest, float multiplier, int numValues) noexcept; | |||||
/** Multiplies each of the destination values by a fixed multiplier. */ | |||||
static void JUCE_CALLTYPE multiply (double* dest, double multiplier, int numValues) noexcept; | |||||
/** Multiplies each of the source values by a fixed multiplier and stores the result in the destination array. */ | |||||
static void JUCE_CALLTYPE multiply (float* dest, const float* src, float multiplier, int num) noexcept; | |||||
/** Multiplies each of the source values by a fixed multiplier and stores the result in the destination array. */ | |||||
static void JUCE_CALLTYPE multiply (double* dest, const double* src, double multiplier, int num) noexcept; | |||||
/** Copies a source vector to a destination, negating each value. */ | |||||
static void JUCE_CALLTYPE negate (float* dest, const float* src, int numValues) noexcept; | |||||
/** Copies a source vector to a destination, negating each value. */ | |||||
static void JUCE_CALLTYPE negate (double* dest, const double* src, int numValues) noexcept; | |||||
/** Copies a source vector to a destination, taking the absolute of each value. */ | |||||
static void JUCE_CALLTYPE abs (float* dest, const float* src, int numValues) noexcept; | |||||
/** Copies a source vector to a destination, taking the absolute of each value. */ | |||||
static void JUCE_CALLTYPE abs (double* dest, const double* src, int numValues) noexcept; | |||||
/** Converts a stream of integers to floats, multiplying each one by the given multiplier. */ | |||||
static void JUCE_CALLTYPE convertFixedToFloat (float* dest, const int* src, float multiplier, int numValues) noexcept; | |||||
/** Each element of dest will be the minimum of the corresponding element of the source array and the given comp value. */ | |||||
static void JUCE_CALLTYPE min (float* dest, const float* src, float comp, int num) noexcept; | |||||
/** Each element of dest will be the minimum of the corresponding element of the source array and the given comp value. */ | |||||
static void JUCE_CALLTYPE min (double* dest, const double* src, double comp, int num) noexcept; | |||||
/** Each element of dest will be the minimum of the corresponding source1 and source2 values. */ | |||||
static void JUCE_CALLTYPE min (float* dest, const float* src1, const float* src2, int num) noexcept; | |||||
/** Each element of dest will be the minimum of the corresponding source1 and source2 values. */ | |||||
static void JUCE_CALLTYPE min (double* dest, const double* src1, const double* src2, int num) noexcept; | |||||
/** Each element of dest will be the maximum of the corresponding element of the source array and the given comp value. */ | |||||
static void JUCE_CALLTYPE max (float* dest, const float* src, float comp, int num) noexcept; | |||||
/** Each element of dest will be the maximum of the corresponding element of the source array and the given comp value. */ | |||||
static void JUCE_CALLTYPE max (double* dest, const double* src, double comp, int num) noexcept; | |||||
/** Each element of dest will be the maximum of the corresponding source1 and source2 values. */ | |||||
static void JUCE_CALLTYPE max (float* dest, const float* src1, const float* src2, int num) noexcept; | |||||
/** Each element of dest will be the maximum of the corresponding source1 and source2 values. */ | |||||
static void JUCE_CALLTYPE max (double* dest, const double* src1, const double* src2, int num) noexcept; | |||||
/** Each element of dest is calculated by hard clipping the corresponding src element so that it is in the range specified by the arguments low and high. */ | |||||
static void JUCE_CALLTYPE clip (float* dest, const float* src, float low, float high, int num) noexcept; | |||||
/** Each element of dest is calculated by hard clipping the corresponding src element so that it is in the range specified by the arguments low and high. */ | |||||
static void JUCE_CALLTYPE clip (double* dest, const double* src, double low, double high, int num) noexcept; | |||||
/** Finds the miniumum and maximum values in the given array. */ | |||||
static Range<float> JUCE_CALLTYPE findMinAndMax (const float* src, int numValues) noexcept; | |||||
/** Finds the miniumum and maximum values in the given array. */ | |||||
static Range<double> JUCE_CALLTYPE findMinAndMax (const double* src, int numValues) noexcept; | |||||
/** Finds the miniumum value in the given array. */ | |||||
static float JUCE_CALLTYPE findMinimum (const float* src, int numValues) noexcept; | |||||
/** Finds the miniumum value in the given array. */ | |||||
static double JUCE_CALLTYPE findMinimum (const double* src, int numValues) noexcept; | |||||
/** Finds the maximum value in the given array. */ | |||||
static float JUCE_CALLTYPE findMaximum (const float* src, int numValues) noexcept; | |||||
/** Finds the maximum value in the given array. */ | |||||
static double JUCE_CALLTYPE findMaximum (const double* src, int numValues) noexcept; | |||||
/** On Intel CPUs, this method enables or disables the SSE flush-to-zero mode. | |||||
Effectively, this is a wrapper around a call to _MM_SET_FLUSH_ZERO_MODE | |||||
*/ | |||||
static void JUCE_CALLTYPE enableFlushToZeroMode (bool shouldEnable) noexcept; | |||||
/** On Intel CPUs, this method enables the SSE flush-to-zero and denormalised-are-zero modes. | |||||
This effectively sets the DAZ and FZ bits of the MXCSR register. It's a convenient thing to | |||||
call before audio processing code where you really want to avoid denormalisation performance hits. | |||||
*/ | |||||
static void JUCE_CALLTYPE disableDenormalisedNumberSupport() noexcept; | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
Helper class providing an RAII-based mechanism for temporarily disabling | |||||
denormals on your CPU. | |||||
*/ | |||||
class ScopedNoDenormals | |||||
{ | |||||
public: | |||||
inline ScopedNoDenormals() noexcept | |||||
{ | |||||
#if JUCE_USE_SSE_INTRINSICS | |||||
mxcsr = _mm_getcsr(); | |||||
_mm_setcsr (mxcsr | 0x8040); // add the DAZ and FZ bits | |||||
#endif | |||||
} | |||||
inline ~ScopedNoDenormals() noexcept | |||||
{ | |||||
#if JUCE_USE_SSE_INTRINSICS | |||||
_mm_setcsr (mxcsr); | |||||
#endif | |||||
} | |||||
private: | |||||
#if JUCE_USE_SSE_INTRINSICS | |||||
unsigned int mxcsr; | |||||
#endif | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,65 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
struct CatmullRomAlgorithm | |||||
{ | |||||
static forcedinline float valueAtOffset (const float* const inputs, const float offset) noexcept | |||||
{ | |||||
auto y0 = inputs[3]; | |||||
auto y1 = inputs[2]; | |||||
auto y2 = inputs[1]; | |||||
auto y3 = inputs[0]; | |||||
auto halfY0 = 0.5f * y0; | |||||
auto halfY3 = 0.5f * y3; | |||||
return y1 + offset * ((0.5f * y2 - halfY0) | |||||
+ (offset * (((y0 + 2.0f * y2) - (halfY3 + 2.5f * y1)) | |||||
+ (offset * ((halfY3 + 1.5f * y1) - (halfY0 + 1.5f * y2)))))); | |||||
} | |||||
}; | |||||
CatmullRomInterpolator::CatmullRomInterpolator() noexcept { reset(); } | |||||
CatmullRomInterpolator::~CatmullRomInterpolator() noexcept {} | |||||
void CatmullRomInterpolator::reset() noexcept | |||||
{ | |||||
subSamplePos = 1.0; | |||||
for (auto& s : lastInputSamples) | |||||
s = 0; | |||||
} | |||||
int CatmullRomInterpolator::process (double actualRatio, const float* in, float* out, int numOut) noexcept | |||||
{ | |||||
return interpolate<CatmullRomAlgorithm> (lastInputSamples, subSamplePos, actualRatio, in, out, numOut); | |||||
} | |||||
int CatmullRomInterpolator::processAdding (double actualRatio, const float* in, float* out, int numOut, float gain) noexcept | |||||
{ | |||||
return interpolateAdding<CatmullRomAlgorithm> (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, gain); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,91 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
/** | |||||
Interpolator for resampling a stream of floats using Catmull-Rom 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 CatmullRomInterpolator | |||||
object. | |||||
@see LagrangeInterpolator | |||||
*/ | |||||
class JUCE_API CatmullRomInterpolator | |||||
{ | |||||
public: | |||||
CatmullRomInterpolator() noexcept; | |||||
~CatmullRomInterpolator() noexcept; | |||||
/** 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 (CatmullRomInterpolator) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,100 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
This class contains some helpful static methods for dealing with decibel values. | |||||
*/ | |||||
class Decibels | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Converts a dBFS value to its equivalent gain level. | |||||
A gain of 1.0 = 0 dB, and lower gains map onto negative decibel values. Any | |||||
decibel value lower than minusInfinityDb will return a gain of 0. | |||||
*/ | |||||
template <typename Type> | |||||
static Type decibelsToGain (const Type decibels, | |||||
const Type minusInfinityDb = (Type) defaultMinusInfinitydB) | |||||
{ | |||||
return decibels > minusInfinityDb ? std::pow ((Type) 10.0, decibels * (Type) 0.05) | |||||
: Type(); | |||||
} | |||||
/** Converts a gain level into a dBFS value. | |||||
A gain of 1.0 = 0 dB, and lower gains map onto negative decibel values. | |||||
If the gain is 0 (or negative), then the method will return the value | |||||
provided as minusInfinityDb. | |||||
*/ | |||||
template <typename Type> | |||||
static Type gainToDecibels (const Type gain, | |||||
const Type minusInfinityDb = (Type) defaultMinusInfinitydB) | |||||
{ | |||||
return gain > Type() ? jmax (minusInfinityDb, (Type) std::log10 (gain) * (Type) 20.0) | |||||
: minusInfinityDb; | |||||
} | |||||
//============================================================================== | |||||
/** Converts a decibel reading to a string, with the 'dB' suffix. | |||||
If the decibel value is lower than minusInfinityDb, the return value will | |||||
be "-INF dB". | |||||
*/ | |||||
template <typename Type> | |||||
static String toString (const Type decibels, | |||||
const int decimalPlaces = 2, | |||||
const Type minusInfinityDb = (Type) defaultMinusInfinitydB) | |||||
{ | |||||
String s; | |||||
if (decibels <= minusInfinityDb) | |||||
{ | |||||
s = "-INF dB"; | |||||
} | |||||
else | |||||
{ | |||||
if (decibels >= Type()) | |||||
s << '+'; | |||||
s << String (decibels, decimalPlaces) << " dB"; | |||||
} | |||||
return s; | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
enum | |||||
{ | |||||
defaultMinusInfinitydB = -100 | |||||
}; | |||||
Decibels(); // This class can't be instantiated, it's just a holder for static methods.. | |||||
JUCE_DECLARE_NON_COPYABLE (Decibels) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,341 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
IIRCoefficients::IIRCoefficients() noexcept | |||||
{ | |||||
zeromem (coefficients, sizeof (coefficients)); | |||||
} | |||||
IIRCoefficients::~IIRCoefficients() noexcept {} | |||||
IIRCoefficients::IIRCoefficients (const IIRCoefficients& other) noexcept | |||||
{ | |||||
memcpy (coefficients, other.coefficients, sizeof (coefficients)); | |||||
} | |||||
IIRCoefficients& IIRCoefficients::operator= (const IIRCoefficients& other) noexcept | |||||
{ | |||||
memcpy (coefficients, other.coefficients, sizeof (coefficients)); | |||||
return *this; | |||||
} | |||||
IIRCoefficients::IIRCoefficients (double c1, double c2, double c3, | |||||
double c4, double c5, double c6) noexcept | |||||
{ | |||||
const double a = 1.0 / c4; | |||||
coefficients[0] = (float) (c1 * a); | |||||
coefficients[1] = (float) (c2 * a); | |||||
coefficients[2] = (float) (c3 * a); | |||||
coefficients[3] = (float) (c5 * a); | |||||
coefficients[4] = (float) (c6 * a); | |||||
} | |||||
IIRCoefficients IIRCoefficients::makeLowPass (const double sampleRate, | |||||
const double frequency) noexcept | |||||
{ | |||||
return makeLowPass (sampleRate, frequency, 1.0 / std::sqrt (2.0)); | |||||
} | |||||
IIRCoefficients IIRCoefficients::makeLowPass (const double sampleRate, | |||||
const double frequency, | |||||
const double Q) noexcept | |||||
{ | |||||
jassert (sampleRate > 0.0); | |||||
jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); | |||||
jassert (Q > 0.0); | |||||
const double n = 1.0 / std::tan (double_Pi * frequency / sampleRate); | |||||
const double nSquared = n * n; | |||||
const double c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); | |||||
return IIRCoefficients (c1, | |||||
c1 * 2.0, | |||||
c1, | |||||
1.0, | |||||
c1 * 2.0 * (1.0 - nSquared), | |||||
c1 * (1.0 - 1.0 / Q * n + nSquared)); | |||||
} | |||||
IIRCoefficients IIRCoefficients::makeHighPass (const double sampleRate, | |||||
const double frequency) noexcept | |||||
{ | |||||
return makeHighPass (sampleRate, frequency, 1.0 / std::sqrt(2.0)); | |||||
} | |||||
IIRCoefficients IIRCoefficients::makeHighPass (const double sampleRate, | |||||
const double frequency, | |||||
const double Q) noexcept | |||||
{ | |||||
jassert (sampleRate > 0.0); | |||||
jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); | |||||
jassert (Q > 0.0); | |||||
const double n = std::tan (double_Pi * frequency / sampleRate); | |||||
const double nSquared = n * n; | |||||
const double c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); | |||||
return IIRCoefficients (c1, | |||||
c1 * -2.0, | |||||
c1, | |||||
1.0, | |||||
c1 * 2.0 * (nSquared - 1.0), | |||||
c1 * (1.0 - 1.0 / Q * n + nSquared)); | |||||
} | |||||
IIRCoefficients IIRCoefficients::makeBandPass (const double sampleRate, | |||||
const double frequency) noexcept | |||||
{ | |||||
return makeBandPass (sampleRate, frequency, 1.0 / std::sqrt (2.0)); | |||||
} | |||||
IIRCoefficients IIRCoefficients::makeBandPass (const double sampleRate, | |||||
const double frequency, | |||||
const double Q) noexcept | |||||
{ | |||||
jassert (sampleRate > 0.0); | |||||
jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); | |||||
jassert (Q > 0.0); | |||||
const double n = 1.0 / std::tan (double_Pi * frequency / sampleRate); | |||||
const double nSquared = n * n; | |||||
const double c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); | |||||
return IIRCoefficients (c1 * n / Q, | |||||
0.0, | |||||
-c1 * n / Q, | |||||
1.0, | |||||
c1 * 2.0 * (1.0 - nSquared), | |||||
c1 * (1.0 - 1.0 / Q * n + nSquared)); | |||||
} | |||||
IIRCoefficients IIRCoefficients::makeNotchFilter (const double sampleRate, | |||||
const double frequency) noexcept | |||||
{ | |||||
return makeNotchFilter (sampleRate, frequency, 1.0 / std::sqrt (2.0)); | |||||
} | |||||
IIRCoefficients IIRCoefficients::makeNotchFilter (const double sampleRate, | |||||
const double frequency, | |||||
const double Q) noexcept | |||||
{ | |||||
jassert (sampleRate > 0.0); | |||||
jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); | |||||
jassert (Q > 0.0); | |||||
const double n = 1.0 / std::tan (double_Pi * frequency / sampleRate); | |||||
const double nSquared = n * n; | |||||
const double c1 = 1.0 / (1.0 + n / Q + nSquared); | |||||
return IIRCoefficients (c1 * (1.0 + nSquared), | |||||
2.0 * c1 * (1.0 - nSquared), | |||||
c1 * (1.0 + nSquared), | |||||
1.0, | |||||
c1 * 2.0 * (1.0 - nSquared), | |||||
c1 * (1.0 - n / Q + nSquared)); | |||||
} | |||||
IIRCoefficients IIRCoefficients::makeAllPass (const double sampleRate, | |||||
const double frequency) noexcept | |||||
{ | |||||
return makeAllPass (sampleRate, frequency, 1.0 / std::sqrt (2.0)); | |||||
} | |||||
IIRCoefficients IIRCoefficients::makeAllPass (const double sampleRate, | |||||
const double frequency, | |||||
const double Q) noexcept | |||||
{ | |||||
jassert (sampleRate > 0.0); | |||||
jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); | |||||
jassert (Q > 0.0); | |||||
const double n = 1.0 / std::tan (double_Pi * frequency / sampleRate); | |||||
const double nSquared = n * n; | |||||
const double c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); | |||||
return IIRCoefficients (c1 * (1.0 - n / Q + nSquared), | |||||
c1 * 2.0 * (1.0 - nSquared), | |||||
1.0, | |||||
1.0, | |||||
c1 * 2.0 * (1.0 - nSquared), | |||||
c1 * (1.0 - n / Q + nSquared)); | |||||
} | |||||
IIRCoefficients IIRCoefficients::makeLowShelf (const double sampleRate, | |||||
const double cutOffFrequency, | |||||
const double Q, | |||||
const float gainFactor) noexcept | |||||
{ | |||||
jassert (sampleRate > 0.0); | |||||
jassert (cutOffFrequency > 0.0 && cutOffFrequency <= sampleRate * 0.5); | |||||
jassert (Q > 0.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.0); | |||||
jassert (cutOffFrequency > 0.0 && cutOffFrequency <= sampleRate * 0.5); | |||||
jassert (Q > 0.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 frequency, | |||||
const double Q, | |||||
const float gainFactor) noexcept | |||||
{ | |||||
jassert (sampleRate > 0.0); | |||||
jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); | |||||
jassert (Q > 0.0); | |||||
const double A = jmax (0.0f, std::sqrt (gainFactor)); | |||||
const double omega = (double_Pi * 2.0 * jmax (frequency, 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.0), v2 (0.0), active (false) | |||||
{ | |||||
} | |||||
IIRFilter::IIRFilter (const IIRFilter& other) noexcept | |||||
: v1 (0.0), v2 (0.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.0; | |||||
} | |||||
float IIRFilter::processSingleSampleRaw (const float in) noexcept | |||||
{ | |||||
float out = coefficients.coefficients[0] * in + v1; | |||||
JUCE_SNAP_TO_ZERO (out); | |||||
v1 = coefficients.coefficients[1] * in - coefficients.coefficients[3] * out + v2; | |||||
v2 = coefficients.coefficients[2] * in - coefficients.coefficients[4] * out; | |||||
return out; | |||||
} | |||||
void IIRFilter::processSamples (float* const samples, const int numSamples) noexcept | |||||
{ | |||||
const SpinLock::ScopedLockType sl (processLock); | |||||
if (active) | |||||
{ | |||||
const float c0 = coefficients.coefficients[0]; | |||||
const float c1 = coefficients.coefficients[1]; | |||||
const float c2 = coefficients.coefficients[2]; | |||||
const float c3 = coefficients.coefficients[3]; | |||||
const float c4 = coefficients.coefficients[4]; | |||||
float lv1 = v1, lv2 = v2; | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
const float in = samples[i]; | |||||
const float out = c0 * in + lv1; | |||||
samples[i] = out; | |||||
lv1 = c1 * in - c3 * out + lv2; | |||||
lv2 = c2 * in - c4 * out; | |||||
} | |||||
JUCE_SNAP_TO_ZERO (lv1); v1 = lv1; | |||||
JUCE_SNAP_TO_ZERO (lv2); v2 = lv2; | |||||
} | |||||
} | |||||
#undef JUCE_SNAP_TO_ZERO | |||||
} // namespace juce |
@@ -0,0 +1,210 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
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 low-pass filter with variable Q. */ | |||||
static IIRCoefficients makeLowPass (double sampleRate, | |||||
double frequency, | |||||
double Q) noexcept; | |||||
//============================================================================== | |||||
/** Returns the coefficients for a high-pass filter. */ | |||||
static IIRCoefficients makeHighPass (double sampleRate, | |||||
double frequency) noexcept; | |||||
/** Returns the coefficients for a high-pass filter with variable Q. */ | |||||
static IIRCoefficients makeHighPass (double sampleRate, | |||||
double frequency, | |||||
double Q) noexcept; | |||||
//============================================================================== | |||||
/** Returns the coefficients for a band-pass filter. */ | |||||
static IIRCoefficients makeBandPass (double sampleRate, double frequency) noexcept; | |||||
/** Returns the coefficients for a band-pass filter with variable Q. */ | |||||
static IIRCoefficients makeBandPass (double sampleRate, | |||||
double frequency, | |||||
double Q) noexcept; | |||||
//============================================================================== | |||||
/** Returns the coefficients for a notch filter. */ | |||||
static IIRCoefficients makeNotchFilter (double sampleRate, double frequency) noexcept; | |||||
/** Returns the coefficients for a notch filter with variable Q. */ | |||||
static IIRCoefficients makeNotchFilter (double sampleRate, | |||||
double frequency, | |||||
double Q) noexcept; | |||||
//============================================================================== | |||||
/** Returns the coefficients for an all-pass filter. */ | |||||
static IIRCoefficients makeAllPass (double sampleRate, double frequency) noexcept; | |||||
/** Returns the coefficients for an all-pass filter with variable Q. */ | |||||
static IIRCoefficients makeAllPass (double sampleRate, | |||||
double frequency, | |||||
double Q) noexcept; | |||||
//============================================================================== | |||||
/** Returns the coefficients for a low-pass shelf filter with variable Q and gain. | |||||
The gain is a scale factor that the low frequencies are multiplied by, so values | |||||
greater than 1.0 will boost the low frequencies, values less than 1.0 will | |||||
attenuate them. | |||||
*/ | |||||
static IIRCoefficients makeLowShelf (double sampleRate, | |||||
double cutOffFrequency, | |||||
double Q, | |||||
float gainFactor) noexcept; | |||||
/** Returns the coefficients for a high-pass shelf filter with variable Q and gain. | |||||
The gain is a scale factor that the high frequencies are multiplied by, so values | |||||
greater than 1.0 will boost the high frequencies, values less than 1.0 will | |||||
attenuate them. | |||||
*/ | |||||
static IIRCoefficients makeHighShelf (double sampleRate, | |||||
double cutOffFrequency, | |||||
double Q, | |||||
float gainFactor) noexcept; | |||||
/** Returns the coefficients for a peak filter centred around a | |||||
given frequency, with a variable Q and gain. | |||||
The gain is a scale factor that the centre frequencies are multiplied by, so | |||||
values greater than 1.0 will boost the centre frequencies, values less than | |||||
1.0 will attenuate them. | |||||
*/ | |||||
static IIRCoefficients makePeakFilter (double sampleRate, | |||||
double centreFrequency, | |||||
double Q, | |||||
float gainFactor) noexcept; | |||||
//============================================================================== | |||||
/** The raw coefficients. | |||||
You should leave these numbers alone unless you really know what you're doing. | |||||
*/ | |||||
float coefficients[5]; | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
An IIR filter that can perform low, high, or band-pass filtering on an | |||||
audio signal. | |||||
@see IIRCoefficient, IIRFilterAudioSource | |||||
*/ | |||||
class JUCE_API IIRFilter | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a filter. | |||||
Initially the filter is inactive, so will have no effect on samples that | |||||
you process with it. Use the setCoefficients() method to turn it into the | |||||
type of filter needed. | |||||
*/ | |||||
IIRFilter() noexcept; | |||||
/** Creates a copy of another filter. */ | |||||
IIRFilter (const IIRFilter&) noexcept; | |||||
/** Destructor. */ | |||||
~IIRFilter() noexcept; | |||||
//============================================================================== | |||||
/** Clears the filter so that any incoming data passes through unchanged. */ | |||||
void makeInactive() noexcept; | |||||
/** Applies a set of coefficients to this filter. */ | |||||
void setCoefficients (const IIRCoefficients& newCoefficients) noexcept; | |||||
/** Returns the coefficients that this filter is using. */ | |||||
IIRCoefficients getCoefficients() const noexcept { return coefficients; } | |||||
//============================================================================== | |||||
/** Resets the filter's processing pipeline, ready to start a new stream of data. | |||||
Note that this clears the processing state, but the type of filter and | |||||
its coefficients aren't changed. To put a filter into an inactive state, use | |||||
the makeInactive() method. | |||||
*/ | |||||
void reset() noexcept; | |||||
/** Performs the filter operation on the given set of samples. */ | |||||
void processSamples (float* samples, int numSamples) noexcept; | |||||
/** Processes a single sample, without any locking or checking. | |||||
Use this if you need fast processing of a single value, but be aware that | |||||
this isn't thread-safe in the way that processSamples() is. | |||||
*/ | |||||
float processSingleSampleRaw (float sample) noexcept; | |||||
protected: | |||||
//============================================================================== | |||||
SpinLock processLock; | |||||
IIRCoefficients coefficients; | |||||
float v1, v2; | |||||
bool active; | |||||
IIRFilter& operator= (const IIRFilter&); | |||||
JUCE_LEAK_DETECTOR (IIRFilter) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,244 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#if JUCE_INTEL | |||||
#define JUCE_SNAP_TO_ZERO(n) if (! (n < -1.0e-8 || n > 1.0e-8)) n = 0; | |||||
#else | |||||
#define JUCE_SNAP_TO_ZERO(n) | |||||
#endif | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
IIRFilterOld::IIRFilterOld() | |||||
: active (false), v1 (0), v2 (0) | |||||
{ | |||||
zeromem (coefficients, sizeof (coefficients)); | |||||
} | |||||
IIRFilterOld::IIRFilterOld (const IIRFilterOld& other) | |||||
: active (other.active), v1 (0), v2 (0) | |||||
{ | |||||
const SpinLock::ScopedLockType sl (other.processLock); | |||||
memcpy (coefficients, other.coefficients, sizeof (coefficients)); | |||||
} | |||||
IIRFilterOld::~IIRFilterOld() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
void IIRFilterOld::reset() noexcept | |||||
{ | |||||
const SpinLock::ScopedLockType sl (processLock); | |||||
v1 = v2 = 0; | |||||
} | |||||
float IIRFilterOld::processSingleSampleRaw (const float in) noexcept | |||||
{ | |||||
float out = coefficients[0] * in + v1; | |||||
JUCE_SNAP_TO_ZERO (out); | |||||
v1 = coefficients[1] * in - coefficients[3] * out + v2; | |||||
v2 = coefficients[2] * in - coefficients[4] * out; | |||||
return out; | |||||
} | |||||
void IIRFilterOld::processSamples (float* const samples, | |||||
const int numSamples) noexcept | |||||
{ | |||||
const SpinLock::ScopedLockType sl (processLock); | |||||
if (active) | |||||
{ | |||||
const float c0 = coefficients[0]; | |||||
const float c1 = coefficients[1]; | |||||
const float c2 = coefficients[2]; | |||||
const float c3 = coefficients[3]; | |||||
const float c4 = coefficients[4]; | |||||
float lv1 = v1, lv2 = v2; | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
const float in = samples[i]; | |||||
const float out = c0 * in + lv1; | |||||
samples[i] = out; | |||||
lv1 = c1 * in - c3 * out + lv2; | |||||
lv2 = c2 * in - c4 * out; | |||||
} | |||||
JUCE_SNAP_TO_ZERO (lv1); v1 = lv1; | |||||
JUCE_SNAP_TO_ZERO (lv2); v2 = lv2; | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void IIRFilterOld::makeLowPass (const double sampleRate, | |||||
const double frequency) noexcept | |||||
{ | |||||
jassert (sampleRate > 0); | |||||
const double n = 1.0 / tan (double_Pi * frequency / sampleRate); | |||||
const double nSquared = n * n; | |||||
const double c1 = 1.0 / (1.0 + std::sqrt (2.0) * n + nSquared); | |||||
setCoefficients (c1, | |||||
c1 * 2.0f, | |||||
c1, | |||||
1.0, | |||||
c1 * 2.0 * (1.0 - nSquared), | |||||
c1 * (1.0 - std::sqrt (2.0) * n + nSquared)); | |||||
} | |||||
void IIRFilterOld::makeHighPass (const double sampleRate, | |||||
const double frequency) noexcept | |||||
{ | |||||
const double n = tan (double_Pi * frequency / sampleRate); | |||||
const double nSquared = n * n; | |||||
const double c1 = 1.0 / (1.0 + std::sqrt (2.0) * n + nSquared); | |||||
setCoefficients (c1, | |||||
c1 * -2.0f, | |||||
c1, | |||||
1.0, | |||||
c1 * 2.0 * (nSquared - 1.0), | |||||
c1 * (1.0 - std::sqrt (2.0) * n + nSquared)); | |||||
} | |||||
void IIRFilterOld::makeLowShelf (const double sampleRate, | |||||
const double cutOffFrequency, | |||||
const double Q, | |||||
const float gainFactor) noexcept | |||||
{ | |||||
jassert (sampleRate > 0); | |||||
jassert (Q > 0); | |||||
const double A = jmax (0.0f, gainFactor); | |||||
const double aminus1 = A - 1.0; | |||||
const double aplus1 = A + 1.0; | |||||
const double omega = (double_Pi * 2.0 * jmax (cutOffFrequency, 2.0)) / sampleRate; | |||||
const double coso = std::cos (omega); | |||||
const double beta = std::sin (omega) * std::sqrt (A) / Q; | |||||
const double aminus1TimesCoso = aminus1 * coso; | |||||
setCoefficients (A * (aplus1 - aminus1TimesCoso + beta), | |||||
A * 2.0 * (aminus1 - aplus1 * coso), | |||||
A * (aplus1 - aminus1TimesCoso - beta), | |||||
aplus1 + aminus1TimesCoso + beta, | |||||
-2.0 * (aminus1 + aplus1 * coso), | |||||
aplus1 + aminus1TimesCoso - beta); | |||||
} | |||||
void IIRFilterOld::makeHighShelf (const double sampleRate, | |||||
const double cutOffFrequency, | |||||
const double Q, | |||||
const float gainFactor) noexcept | |||||
{ | |||||
jassert (sampleRate > 0); | |||||
jassert (Q > 0); | |||||
const double A = jmax (0.0f, gainFactor); | |||||
const double aminus1 = A - 1.0; | |||||
const double aplus1 = A + 1.0; | |||||
const double omega = (double_Pi * 2.0 * jmax (cutOffFrequency, 2.0)) / sampleRate; | |||||
const double coso = std::cos (omega); | |||||
const double beta = std::sin (omega) * std::sqrt (A) / Q; | |||||
const double aminus1TimesCoso = aminus1 * coso; | |||||
setCoefficients (A * (aplus1 + aminus1TimesCoso + beta), | |||||
A * -2.0 * (aminus1 + aplus1 * coso), | |||||
A * (aplus1 + aminus1TimesCoso - beta), | |||||
aplus1 - aminus1TimesCoso + beta, | |||||
2.0 * (aminus1 - aplus1 * coso), | |||||
aplus1 - aminus1TimesCoso - beta); | |||||
} | |||||
void IIRFilterOld::makeBandPass (const double sampleRate, | |||||
const double centreFrequency, | |||||
const double Q, | |||||
const float gainFactor) noexcept | |||||
{ | |||||
jassert (sampleRate > 0); | |||||
jassert (Q > 0); | |||||
const double A = jmax (0.0f, gainFactor); | |||||
const double omega = (double_Pi * 2.0 * jmax (centreFrequency, 2.0)) / sampleRate; | |||||
const double alpha = 0.5 * std::sin (omega) / Q; | |||||
const double c2 = -2.0 * std::cos (omega); | |||||
const double alphaTimesA = alpha * A; | |||||
const double alphaOverA = alpha / A; | |||||
setCoefficients (1.0 + alphaTimesA, | |||||
c2, | |||||
1.0 - alphaTimesA, | |||||
1.0 + alphaOverA, | |||||
c2, | |||||
1.0 - alphaOverA); | |||||
} | |||||
void IIRFilterOld::makeInactive() noexcept | |||||
{ | |||||
const SpinLock::ScopedLockType sl (processLock); | |||||
active = false; | |||||
} | |||||
//============================================================================== | |||||
void IIRFilterOld::copyCoefficientsFrom (const IIRFilterOld& other) noexcept | |||||
{ | |||||
const SpinLock::ScopedLockType sl (processLock); | |||||
memcpy (coefficients, other.coefficients, sizeof (coefficients)); | |||||
active = other.active; | |||||
} | |||||
//============================================================================== | |||||
void IIRFilterOld::setCoefficients (double c1, double c2, double c3, | |||||
double c4, double c5, double c6) noexcept | |||||
{ | |||||
const double a = 1.0 / c4; | |||||
c1 *= a; | |||||
c2 *= a; | |||||
c3 *= a; | |||||
c5 *= a; | |||||
c6 *= a; | |||||
const SpinLock::ScopedLockType sl (processLock); | |||||
coefficients[0] = (float) c1; | |||||
coefficients[1] = (float) c2; | |||||
coefficients[2] = (float) c3; | |||||
coefficients[3] = (float) c5; | |||||
coefficients[4] = (float) c6; | |||||
active = true; | |||||
} | |||||
#undef JUCE_SNAP_TO_ZERO | |||||
} // namespace juce |
@@ -0,0 +1,153 @@ | |||||
/* | |||||
============================================================================== | |||||
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_OLD_JUCEHEADER__ | |||||
#define __JUCE_IIRFILTER_OLD_JUCEHEADER__ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
An IIR filter that can perform low, high, or band-pass filtering on an | |||||
audio signal. | |||||
@see IIRFilterAudioSource | |||||
*/ | |||||
class JUCE_API IIRFilterOld | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a filter. | |||||
Initially the filter is inactive, so will have no effect on samples that | |||||
you process with it. Use the appropriate method to turn it into the type | |||||
of filter needed. | |||||
*/ | |||||
IIRFilterOld(); | |||||
/** Creates a copy of another filter. */ | |||||
IIRFilterOld (const IIRFilterOld& other); | |||||
/** Destructor. */ | |||||
~IIRFilterOld(); | |||||
//============================================================================== | |||||
/** Resets the filter's processing pipeline, ready to start a new stream of data. | |||||
Note that this clears the processing state, but the type of filter and | |||||
its coefficients aren't changed. To put a filter into an inactive state, use | |||||
the makeInactive() method. | |||||
*/ | |||||
void reset() noexcept; | |||||
/** Performs the filter operation on the given set of samples. | |||||
*/ | |||||
void processSamples (float* samples, | |||||
int numSamples) noexcept; | |||||
/** Processes a single sample, without any locking or checking. | |||||
Use this if you need fast processing of a single value, but be aware that | |||||
this isn't thread-safe in the way that processSamples() is. | |||||
*/ | |||||
float processSingleSampleRaw (float sample) noexcept; | |||||
//============================================================================== | |||||
/** Sets the filter up to act as a low-pass filter. | |||||
*/ | |||||
void makeLowPass (double sampleRate, | |||||
double frequency) noexcept; | |||||
/** Sets the filter up to act as a high-pass filter. | |||||
*/ | |||||
void makeHighPass (double sampleRate, | |||||
double frequency) noexcept; | |||||
//============================================================================== | |||||
/** Sets the filter up to act as a low-pass shelf filter with variable Q and gain. | |||||
The gain is a scale factor that the low frequencies are multiplied by, so values | |||||
greater than 1.0 will boost the low frequencies, values less than 1.0 will | |||||
attenuate them. | |||||
*/ | |||||
void makeLowShelf (double sampleRate, | |||||
double cutOffFrequency, | |||||
double Q, | |||||
float gainFactor) noexcept; | |||||
/** Sets the filter up to act as a high-pass shelf filter with variable Q and gain. | |||||
The gain is a scale factor that the high frequencies are multiplied by, so values | |||||
greater than 1.0 will boost the high frequencies, values less than 1.0 will | |||||
attenuate them. | |||||
*/ | |||||
void makeHighShelf (double sampleRate, | |||||
double cutOffFrequency, | |||||
double Q, | |||||
float gainFactor) noexcept; | |||||
/** Sets the filter up to act as a band pass filter centred around a | |||||
frequency, with a variable Q and gain. | |||||
The gain is a scale factor that the centre frequencies are multiplied by, so | |||||
values greater than 1.0 will boost the centre frequencies, values less than | |||||
1.0 will attenuate them. | |||||
*/ | |||||
void makeBandPass (double sampleRate, | |||||
double centreFrequency, | |||||
double Q, | |||||
float gainFactor) noexcept; | |||||
/** Clears the filter's coefficients so that it becomes inactive. | |||||
*/ | |||||
void makeInactive() noexcept; | |||||
//============================================================================== | |||||
/** Makes this filter duplicate the set-up of another one. | |||||
*/ | |||||
void copyCoefficientsFrom (const IIRFilterOld& other) noexcept; | |||||
protected: | |||||
//============================================================================== | |||||
SpinLock processLock; | |||||
void setCoefficients (double c1, double c2, double c3, | |||||
double c4, double c5, double c6) noexcept; | |||||
bool active; | |||||
float coefficients[5]; | |||||
float v1, v2; | |||||
// (use the copyCoefficientsFrom() method instead of this operator) | |||||
IIRFilterOld& operator= (const IIRFilterOld&); | |||||
JUCE_LEAK_DETECTOR (IIRFilterOld) | |||||
}; | |||||
} // namespace juce | |||||
#endif // __JUCE_IIRFILTER_OLD_JUCEHEADER__ |
@@ -0,0 +1,173 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
namespace | |||||
{ | |||||
static forcedinline void pushInterpolationSample (float* lastInputSamples, const float newValue) noexcept | |||||
{ | |||||
lastInputSamples[4] = lastInputSamples[3]; | |||||
lastInputSamples[3] = lastInputSamples[2]; | |||||
lastInputSamples[2] = lastInputSamples[1]; | |||||
lastInputSamples[1] = lastInputSamples[0]; | |||||
lastInputSamples[0] = newValue; | |||||
} | |||||
static forcedinline void pushInterpolationSamples (float* lastInputSamples, const float* input, int numOut) noexcept | |||||
{ | |||||
if (numOut >= 5) | |||||
{ | |||||
for (int i = 0; i < 5; ++i) | |||||
lastInputSamples[i] = input[--numOut]; | |||||
} | |||||
else | |||||
{ | |||||
for (int i = 0; i < numOut; ++i) | |||||
pushInterpolationSample (lastInputSamples, input[i]); | |||||
} | |||||
} | |||||
template <typename InterpolatorType> | |||||
static int interpolate (float* lastInputSamples, double& subSamplePos, double actualRatio, | |||||
const float* in, float* out, int numOut) noexcept | |||||
{ | |||||
auto pos = subSamplePos; | |||||
if (actualRatio == 1.0 && pos == 1.0) | |||||
{ | |||||
memcpy (out, in, (size_t) numOut * sizeof (float)); | |||||
pushInterpolationSamples (lastInputSamples, in, numOut); | |||||
return numOut; | |||||
} | |||||
int numUsed = 0; | |||||
while (numOut > 0) | |||||
{ | |||||
while (pos >= 1.0) | |||||
{ | |||||
pushInterpolationSample (lastInputSamples, in[numUsed++]); | |||||
pos -= 1.0; | |||||
} | |||||
*out++ = InterpolatorType::valueAtOffset (lastInputSamples, (float) pos); | |||||
pos += actualRatio; | |||||
--numOut; | |||||
} | |||||
subSamplePos = pos; | |||||
return numUsed; | |||||
} | |||||
template <typename InterpolatorType> | |||||
static int interpolateAdding (float* lastInputSamples, double& subSamplePos, double actualRatio, | |||||
const float* in, float* out, int numOut, const float gain) noexcept | |||||
{ | |||||
auto pos = subSamplePos; | |||||
if (actualRatio == 1.0 && pos == 1.0) | |||||
{ | |||||
FloatVectorOperations::addWithMultiply (out, in, gain, numOut); | |||||
pushInterpolationSamples (lastInputSamples, in, numOut); | |||||
return numOut; | |||||
} | |||||
int numUsed = 0; | |||||
while (numOut > 0) | |||||
{ | |||||
while (pos >= 1.0) | |||||
{ | |||||
pushInterpolationSample (lastInputSamples, in[numUsed++]); | |||||
pos -= 1.0; | |||||
} | |||||
*out++ += gain * InterpolatorType::valueAtOffset (lastInputSamples, (float) pos); | |||||
pos += actualRatio; | |||||
--numOut; | |||||
} | |||||
subSamplePos = pos; | |||||
return numUsed; | |||||
} | |||||
} | |||||
//============================================================================== | |||||
template <int k> | |||||
struct LagrangeResampleHelper | |||||
{ | |||||
static forcedinline void calc (float& a, float b) noexcept { a *= b * (1.0f / k); } | |||||
}; | |||||
template<> | |||||
struct LagrangeResampleHelper<0> | |||||
{ | |||||
static forcedinline void calc (float&, float) noexcept {} | |||||
}; | |||||
struct LagrangeAlgorithm | |||||
{ | |||||
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); | |||||
} | |||||
template <int k> | |||||
static forcedinline float calcCoefficient (float input, const float offset) noexcept | |||||
{ | |||||
LagrangeResampleHelper<0 - k>::calc (input, -2.0f - offset); | |||||
LagrangeResampleHelper<1 - k>::calc (input, -1.0f - offset); | |||||
LagrangeResampleHelper<2 - k>::calc (input, 0.0f - offset); | |||||
LagrangeResampleHelper<3 - k>::calc (input, 1.0f - offset); | |||||
LagrangeResampleHelper<4 - k>::calc (input, 2.0f - offset); | |||||
return input; | |||||
} | |||||
}; | |||||
LagrangeInterpolator::LagrangeInterpolator() noexcept { reset(); } | |||||
LagrangeInterpolator::~LagrangeInterpolator() noexcept {} | |||||
void LagrangeInterpolator::reset() noexcept | |||||
{ | |||||
subSamplePos = 1.0; | |||||
for (auto& s : lastInputSamples) | |||||
s = 0; | |||||
} | |||||
int LagrangeInterpolator::process (double actualRatio, const float* in, float* out, int numOut) noexcept | |||||
{ | |||||
return interpolate<LagrangeAlgorithm> (lastInputSamples, subSamplePos, actualRatio, in, out, numOut); | |||||
} | |||||
int LagrangeInterpolator::processAdding (double actualRatio, const float* in, float* out, int numOut, float gain) noexcept | |||||
{ | |||||
return interpolateAdding<LagrangeAlgorithm> (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, gain); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,91 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
/** | |||||
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. | |||||
@see CatmullRomInterpolator | |||||
*/ | |||||
class JUCE_API LagrangeInterpolator | |||||
{ | |||||
public: | |||||
LagrangeInterpolator() noexcept; | |||||
~LagrangeInterpolator() noexcept; | |||||
/** 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) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,186 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Utility class for linearly smoothed values like volume etc. that should | |||||
not change abruptly but as a linear ramp, to avoid audio glitches. | |||||
*/ | |||||
//============================================================================== | |||||
template <typename FloatType> | |||||
class LinearSmoothedValue | |||||
{ | |||||
public: | |||||
/** Constructor. */ | |||||
LinearSmoothedValue() noexcept | |||||
{ | |||||
} | |||||
/** Constructor. */ | |||||
LinearSmoothedValue (FloatType initialValue) noexcept | |||||
: currentValue (initialValue), target (initialValue) | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
/** Reset to a new sample rate and ramp length. | |||||
@param sampleRate The sampling rate | |||||
@param rampLengthInSeconds The duration of the ramp in seconds | |||||
*/ | |||||
void reset (double sampleRate, double rampLengthInSeconds) noexcept | |||||
{ | |||||
jassert (sampleRate > 0 && rampLengthInSeconds >= 0); | |||||
stepsToTarget = (int) std::floor (rampLengthInSeconds * sampleRate); | |||||
currentValue = target; | |||||
countdown = 0; | |||||
} | |||||
//============================================================================== | |||||
/** Set a new target value. | |||||
@param newValue New target value | |||||
*/ | |||||
void setValue (FloatType newValue) noexcept | |||||
{ | |||||
if (target != newValue) | |||||
{ | |||||
target = newValue; | |||||
countdown = stepsToTarget; | |||||
if (countdown <= 0) | |||||
currentValue = target; | |||||
else | |||||
step = (target - currentValue) / (FloatType) countdown; | |||||
} | |||||
} | |||||
//============================================================================== | |||||
/** Compute the next value. | |||||
@returns Smoothed value | |||||
*/ | |||||
FloatType getNextValue() noexcept | |||||
{ | |||||
if (countdown <= 0) | |||||
return target; | |||||
--countdown; | |||||
currentValue += step; | |||||
return currentValue; | |||||
} | |||||
/** Returns true if the current value is currently being interpolated. */ | |||||
bool isSmoothing() const noexcept | |||||
{ | |||||
return countdown > 0; | |||||
} | |||||
/** Returns the target value towards which the smoothed value is currently moving. */ | |||||
FloatType getTargetValue() const noexcept | |||||
{ | |||||
return target; | |||||
} | |||||
//============================================================================== | |||||
/** Applies a linear smoothed gain to a stream of samples | |||||
S[i] *= gain | |||||
@param samples Pointer to a raw array of samples | |||||
@param numSamples Length of array of samples | |||||
*/ | |||||
void applyGain (FloatType* samples, int numSamples) noexcept | |||||
{ | |||||
jassert(numSamples >= 0); | |||||
if (isSmoothing()) | |||||
{ | |||||
for (int i = 0; i < numSamples; i++) | |||||
samples[i] *= getNextValue(); | |||||
} | |||||
else | |||||
{ | |||||
FloatVectorOperations::multiply (samples, target, numSamples); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
/** Computes output as linear smoothed gain applied to a stream of samples. | |||||
Sout[i] = Sin[i] * gain | |||||
@param samplesOut A pointer to a raw array of output samples | |||||
@param samplesIn A pointer to a raw array of input samples | |||||
@param numSamples The length of the array of samples | |||||
*/ | |||||
void applyGain (FloatType* samplesOut, const FloatType* samplesIn, int numSamples) noexcept | |||||
{ | |||||
jassert (numSamples >= 0); | |||||
if (isSmoothing()) | |||||
{ | |||||
for (int i = 0; i < numSamples; i++) | |||||
samplesOut[i] = samplesIn[i] * getNextValue(); | |||||
} | |||||
else | |||||
{ | |||||
FloatVectorOperations::multiply (samplesOut, samplesIn, target, numSamples); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
/** Applies a linear smoothed gain to a buffer */ | |||||
void applyGain (AudioBuffer<FloatType>& buffer, int numSamples) noexcept | |||||
{ | |||||
jassert (numSamples >= 0); | |||||
if (isSmoothing()) | |||||
{ | |||||
if (buffer.getNumChannels() == 1) | |||||
{ | |||||
FloatType* samples = buffer.getWritePointer(0); | |||||
for (int i = 0; i < numSamples; i++) | |||||
samples[i] *= getNextValue(); | |||||
} | |||||
else | |||||
{ | |||||
for (int i = 0; i < numSamples; i++) | |||||
{ | |||||
const FloatType gain = getNextValue(); | |||||
for (int channel = 0; channel < buffer.getNumChannels(); channel++) | |||||
buffer.setSample (channel, i, buffer.getSample (channel, i) * gain); | |||||
} | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
buffer.applyGain (0, numSamples, target); | |||||
} | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
FloatType currentValue = 0, target = 0, step = 0; | |||||
int countdown = 0, stepsToTarget = 0; | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,320 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
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; | |||||
dryGain.setValue (newParams.dryLevel * dryScaleFactor); | |||||
wetGain1.setValue (0.5f * wet * (1.0f + newParams.width)); | |||||
wetGain2.setValue (0.5f * wet * (1.0f - newParams.width)); | |||||
gain = isFrozen (newParams.freezeMode) ? 0.0f : 0.015f; | |||||
parameters = newParams; | |||||
updateDamping(); | |||||
} | |||||
//============================================================================== | |||||
/** 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); | |||||
} | |||||
const double smoothTime = 0.01; | |||||
damping .reset (sampleRate, smoothTime); | |||||
feedback.reset (sampleRate, smoothTime); | |||||
dryGain .reset (sampleRate, smoothTime); | |||||
wetGain1.reset (sampleRate, smoothTime); | |||||
wetGain2.reset (sampleRate, smoothTime); | |||||
} | |||||
/** 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); | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
const float input = (left[i] + right[i]) * gain; | |||||
float outL = 0, outR = 0; | |||||
const float damp = damping.getNextValue(); | |||||
const float feedbck = feedback.getNextValue(); | |||||
for (int j = 0; j < numCombs; ++j) // accumulate the comb filters in parallel | |||||
{ | |||||
outL += comb[0][j].process (input, damp, feedbck); | |||||
outR += comb[1][j].process (input, damp, feedbck); | |||||
} | |||||
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); | |||||
} | |||||
const float dry = dryGain.getNextValue(); | |||||
const float wet1 = wetGain1.getNextValue(); | |||||
const float wet2 = wetGain2.getNextValue(); | |||||
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); | |||||
for (int i = 0; i < numSamples; ++i) | |||||
{ | |||||
const float input = samples[i] * gain; | |||||
float output = 0; | |||||
const float damp = damping.getNextValue(); | |||||
const float feedbck = feedback.getNextValue(); | |||||
for (int j = 0; j < numCombs; ++j) // accumulate the comb filters in parallel | |||||
output += comb[0][j].process (input, damp, feedbck); | |||||
for (int j = 0; j < numAllPasses; ++j) // run the allpass filters in series | |||||
output = allPass[0][j].process (output); | |||||
const float dry = dryGain.getNextValue(); | |||||
const float wet1 = wetGain1.getNextValue(); | |||||
samples[i] = output * wet1 + samples[i] * dry; | |||||
} | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
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; | |||||
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 | |||||
{ | |||||
damping.setValue (dampingToUse); | |||||
feedback.setValue (roomSizeToUse); | |||||
} | |||||
//============================================================================== | |||||
class CombFilter | |||||
{ | |||||
public: | |||||
CombFilter() noexcept : bufferSize (0), bufferIndex (0), last (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); | |||||
} | |||||
float process (const float input, const float damp, const float feedbackLevel) noexcept | |||||
{ | |||||
const float output = buffer[bufferIndex]; | |||||
last = (output * (1.0f - damp)) + (last * damp); | |||||
JUCE_UNDENORMALISE (last); | |||||
float temp = input + (last * feedbackLevel); | |||||
JUCE_UNDENORMALISE (temp); | |||||
buffer[bufferIndex] = temp; | |||||
bufferIndex = (bufferIndex + 1) % bufferSize; | |||||
return output; | |||||
} | |||||
private: | |||||
HeapBlock<float> buffer; | |||||
int bufferSize, bufferIndex; | |||||
float last; | |||||
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); | |||||
} | |||||
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 }; | |||||
Parameters parameters; | |||||
float gain; | |||||
CombFilter comb [numChannels][numCombs]; | |||||
AllPassFilter allPass [numChannels][numAllPasses]; | |||||
LinearSmoothedValue<float> damping, feedback, dryGain, wetGain1, wetGain2; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Reverb) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,109 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
#ifdef JUCE_AUDIO_BASICS_H_INCLUDED | |||||
/* 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 | |||||
#include "juce_audio_basics.h" | |||||
#if JUCE_MINGW | |||||
#define JUCE_USE_SSE_INTRINSICS 0 | |||||
#endif | |||||
#if JUCE_MINGW && ! defined (alloca) | |||||
#define alloca __builtin_alloca | |||||
#endif | |||||
#ifndef JUCE_USE_SSE_INTRINSICS | |||||
#define JUCE_USE_SSE_INTRINSICS 1 | |||||
#endif | |||||
#if ! JUCE_INTEL | |||||
#undef JUCE_USE_SSE_INTRINSICS | |||||
#endif | |||||
#if JUCE_USE_SSE_INTRINSICS | |||||
#include <emmintrin.h> | |||||
#endif | |||||
#ifndef JUCE_USE_VDSP_FRAMEWORK | |||||
#define JUCE_USE_VDSP_FRAMEWORK 1 | |||||
#endif | |||||
#if (JUCE_MAC || JUCE_IOS) && JUCE_USE_VDSP_FRAMEWORK | |||||
#include <Accelerate/Accelerate.h> | |||||
#else | |||||
#undef JUCE_USE_VDSP_FRAMEWORK | |||||
#endif | |||||
#if __ARM_NEON__ && ! (JUCE_USE_VDSP_FRAMEWORK || defined (JUCE_USE_ARM_NEON)) | |||||
#define JUCE_USE_ARM_NEON 1 | |||||
#endif | |||||
#if TARGET_IPHONE_SIMULATOR | |||||
#ifdef JUCE_USE_ARM_NEON | |||||
#undef JUCE_USE_ARM_NEON | |||||
#endif | |||||
#define JUCE_USE_ARM_NEON 0 | |||||
#endif | |||||
#if JUCE_USE_ARM_NEON | |||||
#include <arm_neon.h> | |||||
#endif | |||||
#include "buffers/juce_AudioDataConverters.cpp" | |||||
#include "buffers/juce_FloatVectorOperations.cpp" | |||||
#include "buffers/juce_AudioChannelSet.cpp" | |||||
#include "effects/juce_IIRFilter.cpp" | |||||
#include "effects/juce_IIRFilterOld.cpp" | |||||
#include "effects/juce_LagrangeInterpolator.cpp" | |||||
#include "effects/juce_CatmullRomInterpolator.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 "midi/juce_MidiRPN.cpp" | |||||
#include "mpe/juce_MPEValue.cpp" | |||||
#include "mpe/juce_MPENote.cpp" | |||||
#include "mpe/juce_MPEZone.cpp" | |||||
#include "mpe/juce_MPEZoneLayout.cpp" | |||||
#include "mpe/juce_MPEInstrument.cpp" | |||||
#include "mpe/juce_MPEMessages.cpp" | |||||
#include "mpe/juce_MPESynthesiserBase.cpp" | |||||
#include "mpe/juce_MPESynthesiserVoice.cpp" | |||||
#include "mpe/juce_MPESynthesiser.cpp" | |||||
#include "sources/juce_BufferingAudioSource.cpp" | |||||
#include "sources/juce_ChannelRemappingAudioSource.cpp" | |||||
#include "sources/juce_IIRFilterAudioSource.cpp" | |||||
#include "sources/juce_MemoryAudioSource.cpp" | |||||
#include "sources/juce_MixerAudioSource.cpp" | |||||
#include "sources/juce_ResamplingAudioSource.cpp" | |||||
#include "sources/juce_ReverbAudioSource.cpp" | |||||
#include "sources/juce_ToneGeneratorAudioSource.cpp" | |||||
#include "synthesisers/juce_Synthesiser.cpp" |
@@ -0,0 +1,95 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
/******************************************************************************* | |||||
The block below describes the properties of this module, and is read by | |||||
the Projucer to automatically generate project code that uses it. | |||||
For details about the syntax and how to create or use a module, see the | |||||
JUCE Module Format.txt file. | |||||
BEGIN_JUCE_MODULE_DECLARATION | |||||
ID: juce_audio_basics | |||||
vendor: juce | |||||
version: 5.1.2 | |||||
name: JUCE audio and MIDI data classes | |||||
description: Classes for audio buffer manipulation, midi message handling, synthesis, etc. | |||||
website: http://www.juce.com/juce | |||||
license: ISC | |||||
dependencies: juce_core | |||||
OSXFrameworks: Accelerate | |||||
iOSFrameworks: Accelerate | |||||
END_JUCE_MODULE_DECLARATION | |||||
*******************************************************************************/ | |||||
#pragma once | |||||
#define JUCE_AUDIO_BASICS_H_INCLUDED | |||||
#include <juce_core/juce_core.h> | |||||
//============================================================================== | |||||
#undef Complex // apparently some C libraries actually define these symbols (!) | |||||
#undef Factor | |||||
#include "buffers/juce_AudioDataConverters.h" | |||||
#include "buffers/juce_FloatVectorOperations.h" | |||||
#include "buffers/juce_AudioSampleBuffer.h" | |||||
#include "buffers/juce_AudioChannelSet.h" | |||||
#include "effects/juce_Decibels.h" | |||||
#include "effects/juce_IIRFilter.h" | |||||
#include "effects/juce_IIRFilterOld.h" | |||||
#include "effects/juce_LagrangeInterpolator.h" | |||||
#include "effects/juce_CatmullRomInterpolator.h" | |||||
#include "effects/juce_LinearSmoothedValue.h" | |||||
#include "effects/juce_Reverb.h" | |||||
#include "midi/juce_MidiMessage.h" | |||||
#include "midi/juce_MidiBuffer.h" | |||||
#include "midi/juce_MidiMessageSequence.h" | |||||
#include "midi/juce_MidiFile.h" | |||||
#include "midi/juce_MidiKeyboardState.h" | |||||
#include "midi/juce_MidiRPN.h" | |||||
#include "mpe/juce_MPEValue.h" | |||||
#include "mpe/juce_MPENote.h" | |||||
#include "mpe/juce_MPEZone.h" | |||||
#include "mpe/juce_MPEZoneLayout.h" | |||||
#include "mpe/juce_MPEInstrument.h" | |||||
#include "mpe/juce_MPEMessages.h" | |||||
#include "mpe/juce_MPESynthesiserBase.h" | |||||
#include "mpe/juce_MPESynthesiserVoice.h" | |||||
#include "mpe/juce_MPESynthesiser.h" | |||||
#include "sources/juce_AudioSource.h" | |||||
#include "sources/juce_PositionableAudioSource.h" | |||||
#include "sources/juce_BufferingAudioSource.h" | |||||
#include "sources/juce_ChannelRemappingAudioSource.h" | |||||
#include "sources/juce_IIRFilterAudioSource.h" | |||||
#include "sources/juce_MemoryAudioSource.h" | |||||
#include "sources/juce_MixerAudioSource.h" | |||||
#include "sources/juce_ResamplingAudioSource.h" | |||||
#include "sources/juce_ReverbAudioSource.h" | |||||
#include "sources/juce_ToneGeneratorAudioSource.h" | |||||
#include "synthesisers/juce_Synthesiser.h" | |||||
#include "audio_play_head/juce_AudioPlayHead.h" |
@@ -0,0 +1,232 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
namespace MidiBufferHelpers | |||||
{ | |||||
inline int getEventTime (const void* const d) noexcept | |||||
{ | |||||
return readUnaligned<int32> (d); | |||||
} | |||||
inline uint16 getEventDataSize (const void* const d) noexcept | |||||
{ | |||||
return readUnaligned<uint16> (static_cast<const char*> (d) + sizeof (int32)); | |||||
} | |||||
inline uint16 getEventTotalSize (const void* const d) noexcept | |||||
{ | |||||
return (uint16) (getEventDataSize (d) + sizeof (int32) + sizeof (uint16)); | |||||
} | |||||
static int findActualEventLength (const uint8* const data, const int maxBytes) noexcept | |||||
{ | |||||
unsigned int byte = (unsigned int) *data; | |||||
int size = 0; | |||||
if (byte == 0xf0 || byte == 0xf7) | |||||
{ | |||||
const uint8* d = data + 1; | |||||
while (d < data + maxBytes) | |||||
if (*d++ == 0xf7) | |||||
break; | |||||
size = (int) (d - data); | |||||
} | |||||
else if (byte == 0xff) | |||||
{ | |||||
int n; | |||||
const int bytesLeft = MidiMessage::readVariableLengthVal (data + 1, n); | |||||
size = jmin (maxBytes, n + 2 + bytesLeft); | |||||
} | |||||
else if (byte >= 0x80) | |||||
{ | |||||
size = jmin (maxBytes, MidiMessage::getMessageLengthFromFirstByte ((uint8) byte)); | |||||
} | |||||
return size; | |||||
} | |||||
static uint8* findEventAfter (uint8* d, uint8* endData, const int samplePosition) noexcept | |||||
{ | |||||
while (d < endData && getEventTime (d) <= samplePosition) | |||||
d += getEventTotalSize (d); | |||||
return d; | |||||
} | |||||
} | |||||
//============================================================================== | |||||
MidiBuffer::MidiBuffer() noexcept {} | |||||
MidiBuffer::~MidiBuffer() {} | |||||
MidiBuffer::MidiBuffer (const MidiBuffer& other) noexcept : data (other.data) {} | |||||
MidiBuffer& MidiBuffer::operator= (const MidiBuffer& other) noexcept | |||||
{ | |||||
data = other.data; | |||||
return *this; | |||||
} | |||||
MidiBuffer::MidiBuffer (const MidiMessage& message) noexcept | |||||
{ | |||||
addEvent (message, 0); | |||||
} | |||||
void MidiBuffer::swapWith (MidiBuffer& other) noexcept { data.swapWith (other.data); } | |||||
void MidiBuffer::clear() noexcept { data.clearQuick(); } | |||||
void MidiBuffer::ensureSize (size_t minimumNumBytes) { data.ensureStorageAllocated ((int) minimumNumBytes); } | |||||
bool MidiBuffer::isEmpty() const noexcept { return data.size() == 0; } | |||||
void MidiBuffer::clear (const int startSample, const int numSamples) | |||||
{ | |||||
uint8* const start = MidiBufferHelpers::findEventAfter (data.begin(), data.end(), startSample - 1); | |||||
uint8* const end = MidiBufferHelpers::findEventAfter (start, data.end(), startSample + numSamples - 1); | |||||
data.removeRange ((int) (start - data.begin()), (int) (end - data.begin())); | |||||
} | |||||
void MidiBuffer::addEvent (const MidiMessage& m, const int sampleNumber) | |||||
{ | |||||
addEvent (m.getRawData(), m.getRawDataSize(), sampleNumber); | |||||
} | |||||
void MidiBuffer::addEvent (const void* const newData, const int maxBytes, const int sampleNumber) | |||||
{ | |||||
const int numBytes = MidiBufferHelpers::findActualEventLength (static_cast<const uint8*> (newData), maxBytes); | |||||
if (numBytes > 0) | |||||
{ | |||||
const size_t newItemSize = (size_t) numBytes + sizeof (int32) + sizeof (uint16); | |||||
const int offset = (int) (MidiBufferHelpers::findEventAfter (data.begin(), data.end(), sampleNumber) - data.begin()); | |||||
data.insertMultiple (offset, 0, (int) newItemSize); | |||||
uint8* const d = data.begin() + offset; | |||||
writeUnaligned<int32> (d, sampleNumber); | |||||
writeUnaligned<uint16> (d + 4, static_cast<uint16> (numBytes)); | |||||
memcpy (d + 6, newData, (size_t) numBytes); | |||||
} | |||||
} | |||||
void MidiBuffer::addEvents (const MidiBuffer& otherBuffer, | |||||
const int startSample, | |||||
const int numSamples, | |||||
const int sampleDeltaToAdd) | |||||
{ | |||||
Iterator i (otherBuffer); | |||||
i.setNextSamplePosition (startSample); | |||||
const uint8* eventData; | |||||
int eventSize, position; | |||||
while (i.getNextEvent (eventData, eventSize, position) | |||||
&& (position < startSample + numSamples || numSamples < 0)) | |||||
{ | |||||
addEvent (eventData, eventSize, position + sampleDeltaToAdd); | |||||
} | |||||
} | |||||
int MidiBuffer::getNumEvents() const noexcept | |||||
{ | |||||
int n = 0; | |||||
const uint8* const end = data.end(); | |||||
for (const uint8* d = data.begin(); d < end; ++n) | |||||
d += MidiBufferHelpers::getEventTotalSize (d); | |||||
return n; | |||||
} | |||||
int MidiBuffer::getFirstEventTime() const noexcept | |||||
{ | |||||
return data.size() > 0 ? MidiBufferHelpers::getEventTime (data.begin()) : 0; | |||||
} | |||||
int MidiBuffer::getLastEventTime() const noexcept | |||||
{ | |||||
if (data.size() == 0) | |||||
return 0; | |||||
const uint8* const endData = data.end(); | |||||
for (const uint8* d = data.begin();;) | |||||
{ | |||||
const uint8* const nextOne = d + MidiBufferHelpers::getEventTotalSize (d); | |||||
if (nextOne >= endData) | |||||
return MidiBufferHelpers::getEventTime (d); | |||||
d = nextOne; | |||||
} | |||||
} | |||||
//============================================================================== | |||||
MidiBuffer::Iterator::Iterator (const MidiBuffer& b) noexcept | |||||
: buffer (b), data (b.data.begin()) | |||||
{ | |||||
} | |||||
MidiBuffer::Iterator::~Iterator() noexcept | |||||
{ | |||||
} | |||||
void MidiBuffer::Iterator::setNextSamplePosition (const int samplePosition) noexcept | |||||
{ | |||||
data = buffer.data.begin(); | |||||
const uint8* const dataEnd = buffer.data.end(); | |||||
while (data < dataEnd && MidiBufferHelpers::getEventTime (data) < samplePosition) | |||||
data += MidiBufferHelpers::getEventTotalSize (data); | |||||
} | |||||
bool MidiBuffer::Iterator::getNextEvent (const uint8* &midiData, int& numBytes, int& samplePosition) noexcept | |||||
{ | |||||
if (data >= buffer.data.end()) | |||||
return false; | |||||
samplePosition = MidiBufferHelpers::getEventTime (data); | |||||
const int itemSize = MidiBufferHelpers::getEventDataSize (data); | |||||
numBytes = itemSize; | |||||
midiData = data + sizeof (int32) + sizeof (uint16); | |||||
data += sizeof (int32) + sizeof (uint16) + (size_t) itemSize; | |||||
return true; | |||||
} | |||||
bool MidiBuffer::Iterator::getNextEvent (MidiMessage& result, int& samplePosition) noexcept | |||||
{ | |||||
if (data >= buffer.data.end()) | |||||
return false; | |||||
samplePosition = MidiBufferHelpers::getEventTime (data); | |||||
const int itemSize = MidiBufferHelpers::getEventDataSize (data); | |||||
result = MidiMessage (data + sizeof (int32) + sizeof (uint16), itemSize, samplePosition); | |||||
data += sizeof (int32) + sizeof (uint16) + (size_t) itemSize; | |||||
return true; | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,232 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Holds a sequence of time-stamped midi events. | |||||
Analogous to the AudioSampleBuffer, this holds a set of midi events with | |||||
integer time-stamps. The buffer is kept sorted in order of the time-stamps. | |||||
If you're working with a sequence of midi events that may need to be manipulated | |||||
or read/written to a midi file, then MidiMessageSequence is probably a more | |||||
appropriate container. MidiBuffer is designed for lower-level streams of raw | |||||
midi data. | |||||
@see MidiMessage | |||||
*/ | |||||
class JUCE_API MidiBuffer | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an empty MidiBuffer. */ | |||||
MidiBuffer() noexcept; | |||||
/** Creates a MidiBuffer containing a single midi message. */ | |||||
explicit MidiBuffer (const MidiMessage& message) noexcept; | |||||
/** Creates a copy of another MidiBuffer. */ | |||||
MidiBuffer (const MidiBuffer&) noexcept; | |||||
/** Makes a copy of another MidiBuffer. */ | |||||
MidiBuffer& operator= (const MidiBuffer&) noexcept; | |||||
/** Destructor */ | |||||
~MidiBuffer(); | |||||
//============================================================================== | |||||
/** Removes all events from the buffer. */ | |||||
void clear() noexcept; | |||||
/** Removes all events between two times from the buffer. | |||||
All events for which (start <= event position < start + numSamples) will | |||||
be removed. | |||||
*/ | |||||
void clear (int start, int numSamples); | |||||
/** Returns true if the buffer is empty. | |||||
To actually retrieve the events, use a MidiBuffer::Iterator object | |||||
*/ | |||||
bool isEmpty() const noexcept; | |||||
/** Counts the number of events in the buffer. | |||||
This is actually quite a slow operation, as it has to iterate through all | |||||
the events, so you might prefer to call isEmpty() if that's all you need | |||||
to know. | |||||
*/ | |||||
int getNumEvents() const noexcept; | |||||
/** Adds an event to the buffer. | |||||
The sample number will be used to determine the position of the event in | |||||
the buffer, which is always kept sorted. The MidiMessage's timestamp is | |||||
ignored. | |||||
If an event is added whose sample position is the same as one or more events | |||||
already in the buffer, the new event will be placed after the existing ones. | |||||
To retrieve events, use a MidiBuffer::Iterator object | |||||
*/ | |||||
void addEvent (const MidiMessage& midiMessage, int sampleNumber); | |||||
/** Adds an event to the buffer from raw midi data. | |||||
The sample number will be used to determine the position of the event in | |||||
the buffer, which is always kept sorted. | |||||
If an event is added whose sample position is the same as one or more events | |||||
already in the buffer, the new event will be placed after the existing ones. | |||||
The event data will be inspected to calculate the number of bytes in length that | |||||
the midi event really takes up, so maxBytesOfMidiData may be longer than the data | |||||
that actually gets stored. E.g. if you pass in a note-on and a length of 4 bytes, | |||||
it'll actually only store 3 bytes. If the midi data is invalid, it might not | |||||
add an event at all. | |||||
To retrieve events, use a MidiBuffer::Iterator object | |||||
*/ | |||||
void addEvent (const void* rawMidiData, | |||||
int maxBytesOfMidiData, | |||||
int sampleNumber); | |||||
/** Adds some events from another buffer to this one. | |||||
@param otherBuffer the buffer containing the events you want to add | |||||
@param startSample the lowest sample number in the source buffer for which | |||||
events should be added. Any source events whose timestamp is | |||||
less than this will be ignored | |||||
@param numSamples the valid range of samples from the source buffer for which | |||||
events should be added - i.e. events in the source buffer whose | |||||
timestamp is greater than or equal to (startSample + numSamples) | |||||
will be ignored. If this value is less than 0, all events after | |||||
startSample will be taken. | |||||
@param sampleDeltaToAdd a value which will be added to the source timestamps of the events | |||||
that are added to this buffer | |||||
*/ | |||||
void addEvents (const MidiBuffer& otherBuffer, | |||||
int startSample, | |||||
int numSamples, | |||||
int sampleDeltaToAdd); | |||||
/** Returns the sample number of the first event in the buffer. | |||||
If the buffer's empty, this will just return 0. | |||||
*/ | |||||
int getFirstEventTime() const noexcept; | |||||
/** Returns the sample number of the last event in the buffer. | |||||
If the buffer's empty, this will just return 0. | |||||
*/ | |||||
int getLastEventTime() const noexcept; | |||||
//============================================================================== | |||||
/** Exchanges the contents of this buffer with another one. | |||||
This is a quick operation, because no memory allocating or copying is done, it | |||||
just swaps the internal state of the two buffers. | |||||
*/ | |||||
void swapWith (MidiBuffer&) noexcept; | |||||
/** Preallocates some memory for the buffer to use. | |||||
This helps to avoid needing to reallocate space when the buffer has messages | |||||
added to it. | |||||
*/ | |||||
void ensureSize (size_t minimumNumBytes); | |||||
//============================================================================== | |||||
/** | |||||
Used to iterate through the events in a MidiBuffer. | |||||
Note that altering the buffer while an iterator is using it will produce | |||||
undefined behaviour. | |||||
@see MidiBuffer | |||||
*/ | |||||
class JUCE_API Iterator | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an Iterator for this MidiBuffer. */ | |||||
Iterator (const MidiBuffer&) noexcept; | |||||
/** Creates a copy of an iterator. */ | |||||
Iterator (const Iterator&) noexcept = default; | |||||
/** Destructor. */ | |||||
~Iterator() noexcept; | |||||
//============================================================================== | |||||
/** Repositions the iterator so that the next event retrieved will be the first | |||||
one whose sample position is at greater than or equal to the given position. | |||||
*/ | |||||
void setNextSamplePosition (int samplePosition) noexcept; | |||||
/** Retrieves a copy of the next event from the buffer. | |||||
@param result on return, this will be the message. The MidiMessage's timestamp | |||||
is set to the same value as samplePosition. | |||||
@param samplePosition on return, this will be the position of the event, as a | |||||
sample index in the buffer | |||||
@returns true if an event was found, or false if the iterator has reached | |||||
the end of the buffer | |||||
*/ | |||||
bool getNextEvent (MidiMessage& result, | |||||
int& samplePosition) noexcept; | |||||
/** Retrieves the next event from the buffer. | |||||
@param midiData on return, this pointer will be set to a block of data containing | |||||
the midi message. Note that to make it fast, this is a pointer | |||||
directly into the MidiBuffer's internal data, so is only valid | |||||
temporarily until the MidiBuffer is altered. | |||||
@param numBytesOfMidiData on return, this is the number of bytes of data used by the | |||||
midi message | |||||
@param samplePosition on return, this will be the position of the event, as a | |||||
sample index in the buffer | |||||
@returns true if an event was found, or false if the iterator has reached | |||||
the end of the buffer | |||||
*/ | |||||
bool getNextEvent (const uint8* &midiData, | |||||
int& numBytesOfMidiData, | |||||
int& samplePosition) noexcept; | |||||
private: | |||||
//============================================================================== | |||||
const MidiBuffer& buffer; | |||||
const uint8* data; | |||||
}; | |||||
/** The raw data holding this buffer. | |||||
Obviously access to this data is provided at your own risk. Its internal format could | |||||
change in future, so don't write code that relies on it! | |||||
*/ | |||||
Array<uint8> data; | |||||
private: | |||||
JUCE_LEAK_DETECTOR (MidiBuffer) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,450 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
namespace MidiFileHelpers | |||||
{ | |||||
static void writeVariableLengthInt (OutputStream& out, unsigned int v) | |||||
{ | |||||
unsigned int buffer = v & 0x7f; | |||||
while ((v >>= 7) != 0) | |||||
{ | |||||
buffer <<= 8; | |||||
buffer |= ((v & 0x7f) | 0x80); | |||||
} | |||||
for (;;) | |||||
{ | |||||
out.writeByte ((char) buffer); | |||||
if (buffer & 0x80) | |||||
buffer >>= 8; | |||||
else | |||||
break; | |||||
} | |||||
} | |||||
static bool parseMidiHeader (const uint8* &data, short& timeFormat, short& fileType, short& numberOfTracks) noexcept | |||||
{ | |||||
unsigned int ch = ByteOrder::bigEndianInt (data); | |||||
data += 4; | |||||
if (ch != ByteOrder::bigEndianInt ("MThd")) | |||||
{ | |||||
bool ok = false; | |||||
if (ch == ByteOrder::bigEndianInt ("RIFF")) | |||||
{ | |||||
for (int i = 0; i < 8; ++i) | |||||
{ | |||||
ch = ByteOrder::bigEndianInt (data); | |||||
data += 4; | |||||
if (ch == ByteOrder::bigEndianInt ("MThd")) | |||||
{ | |||||
ok = true; | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
if (! ok) | |||||
return false; | |||||
} | |||||
unsigned int bytesRemaining = ByteOrder::bigEndianInt (data); | |||||
data += 4; | |||||
fileType = (short) ByteOrder::bigEndianShort (data); | |||||
data += 2; | |||||
numberOfTracks = (short) ByteOrder::bigEndianShort (data); | |||||
data += 2; | |||||
timeFormat = (short) ByteOrder::bigEndianShort (data); | |||||
data += 2; | |||||
bytesRemaining -= 6; | |||||
data += bytesRemaining; | |||||
return true; | |||||
} | |||||
static double convertTicksToSeconds (const double time, | |||||
const MidiMessageSequence& tempoEvents, | |||||
const int timeFormat) | |||||
{ | |||||
if (timeFormat < 0) | |||||
return time / (-(timeFormat >> 8) * (timeFormat & 0xff)); | |||||
double lastTime = 0.0, correctedTime = 0.0; | |||||
const double tickLen = 1.0 / (timeFormat & 0x7fff); | |||||
double secsPerTick = 0.5 * tickLen; | |||||
const int numEvents = tempoEvents.getNumEvents(); | |||||
for (int i = 0; i < numEvents; ++i) | |||||
{ | |||||
const MidiMessage& m = tempoEvents.getEventPointer(i)->message; | |||||
const double eventTime = m.getTimeStamp(); | |||||
if (eventTime >= time) | |||||
break; | |||||
correctedTime += (eventTime - lastTime) * secsPerTick; | |||||
lastTime = eventTime; | |||||
if (m.isTempoMetaEvent()) | |||||
secsPerTick = tickLen * m.getTempoSecondsPerQuarterNote(); | |||||
while (i + 1 < numEvents) | |||||
{ | |||||
const MidiMessage& m2 = tempoEvents.getEventPointer(i + 1)->message; | |||||
if (m2.getTimeStamp() != eventTime) | |||||
break; | |||||
if (m2.isTempoMetaEvent()) | |||||
secsPerTick = tickLen * m2.getTempoSecondsPerQuarterNote(); | |||||
++i; | |||||
} | |||||
} | |||||
return correctedTime + (time - lastTime) * secsPerTick; | |||||
} | |||||
// a comparator that puts all the note-offs before note-ons that have the same time | |||||
struct Sorter | |||||
{ | |||||
static int compareElements (const MidiMessageSequence::MidiEventHolder* const first, | |||||
const MidiMessageSequence::MidiEventHolder* const second) noexcept | |||||
{ | |||||
const double diff = (first->message.getTimeStamp() - second->message.getTimeStamp()); | |||||
if (diff > 0) return 1; | |||||
if (diff < 0) return -1; | |||||
if (first->message.isNoteOff() && second->message.isNoteOn()) return -1; | |||||
if (first->message.isNoteOn() && second->message.isNoteOff()) return 1; | |||||
return 0; | |||||
} | |||||
}; | |||||
template <typename MethodType> | |||||
static void findAllMatchingEvents (const OwnedArray<MidiMessageSequence>& tracks, | |||||
MidiMessageSequence& results, | |||||
MethodType method) | |||||
{ | |||||
for (int i = 0; i < tracks.size(); ++i) | |||||
{ | |||||
const MidiMessageSequence& track = *tracks.getUnchecked(i); | |||||
const int numEvents = track.getNumEvents(); | |||||
for (int j = 0; j < numEvents; ++j) | |||||
{ | |||||
const MidiMessage& m = track.getEventPointer(j)->message; | |||||
if ((m.*method)()) | |||||
results.addEvent (m); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
MidiFile::MidiFile() | |||||
: timeFormat ((short) (unsigned short) 0xe728) | |||||
{ | |||||
} | |||||
MidiFile::~MidiFile() | |||||
{ | |||||
} | |||||
MidiFile::MidiFile (const MidiFile& other) | |||||
: timeFormat (other.timeFormat) | |||||
{ | |||||
tracks.addCopiesOf (other.tracks); | |||||
} | |||||
MidiFile& MidiFile::operator= (const MidiFile& other) | |||||
{ | |||||
timeFormat = other.timeFormat; | |||||
tracks.clear(); | |||||
tracks.addCopiesOf (other.tracks); | |||||
return *this; | |||||
} | |||||
void MidiFile::clear() | |||||
{ | |||||
tracks.clear(); | |||||
} | |||||
//============================================================================== | |||||
int MidiFile::getNumTracks() const noexcept | |||||
{ | |||||
return tracks.size(); | |||||
} | |||||
const MidiMessageSequence* MidiFile::getTrack (const int index) const noexcept | |||||
{ | |||||
return tracks [index]; | |||||
} | |||||
void MidiFile::addTrack (const MidiMessageSequence& trackSequence) | |||||
{ | |||||
tracks.add (new MidiMessageSequence (trackSequence)); | |||||
} | |||||
//============================================================================== | |||||
short MidiFile::getTimeFormat() const noexcept | |||||
{ | |||||
return timeFormat; | |||||
} | |||||
void MidiFile::setTicksPerQuarterNote (const int ticks) noexcept | |||||
{ | |||||
timeFormat = (short) ticks; | |||||
} | |||||
void MidiFile::setSmpteTimeFormat (const int framesPerSecond, | |||||
const int subframeResolution) noexcept | |||||
{ | |||||
timeFormat = (short) (((-framesPerSecond) << 8) | subframeResolution); | |||||
} | |||||
//============================================================================== | |||||
void MidiFile::findAllTempoEvents (MidiMessageSequence& results) const | |||||
{ | |||||
MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isTempoMetaEvent); | |||||
} | |||||
void MidiFile::findAllTimeSigEvents (MidiMessageSequence& results) const | |||||
{ | |||||
MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isTimeSignatureMetaEvent); | |||||
} | |||||
void MidiFile::findAllKeySigEvents (MidiMessageSequence& results) const | |||||
{ | |||||
MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isKeySignatureMetaEvent); | |||||
} | |||||
double MidiFile::getLastTimestamp() const | |||||
{ | |||||
double t = 0.0; | |||||
for (int i = tracks.size(); --i >= 0;) | |||||
t = jmax (t, tracks.getUnchecked(i)->getEndTime()); | |||||
return t; | |||||
} | |||||
//============================================================================== | |||||
bool MidiFile::readFrom (InputStream& sourceStream) | |||||
{ | |||||
clear(); | |||||
MemoryBlock data; | |||||
const int maxSensibleMidiFileSize = 200 * 1024 * 1024; | |||||
// (put a sanity-check on the file size, as midi files are generally small) | |||||
if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize)) | |||||
{ | |||||
size_t size = data.getSize(); | |||||
const uint8* d = static_cast<const uint8*> (data.getData()); | |||||
short fileType, expectedTracks; | |||||
if (size > 16 && MidiFileHelpers::parseMidiHeader (d, timeFormat, fileType, expectedTracks)) | |||||
{ | |||||
size -= (size_t) (d - static_cast<const uint8*> (data.getData())); | |||||
int track = 0; | |||||
while (size > 0 && track < expectedTracks) | |||||
{ | |||||
const int chunkType = (int) ByteOrder::bigEndianInt (d); | |||||
d += 4; | |||||
const int chunkSize = (int) ByteOrder::bigEndianInt (d); | |||||
d += 4; | |||||
if (chunkSize <= 0) | |||||
break; | |||||
if (chunkType == (int) ByteOrder::bigEndianInt ("MTrk")) | |||||
readNextTrack (d, chunkSize); | |||||
size -= (size_t) chunkSize + 8; | |||||
d += chunkSize; | |||||
++track; | |||||
} | |||||
return true; | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
void MidiFile::readNextTrack (const uint8* data, int size) | |||||
{ | |||||
double time = 0; | |||||
uint8 lastStatusByte = 0; | |||||
MidiMessageSequence result; | |||||
while (size > 0) | |||||
{ | |||||
int bytesUsed; | |||||
const int delay = MidiMessage::readVariableLengthVal (data, bytesUsed); | |||||
data += bytesUsed; | |||||
size -= bytesUsed; | |||||
time += delay; | |||||
int messSize = 0; | |||||
const MidiMessage mm (data, size, messSize, lastStatusByte, time); | |||||
if (messSize <= 0) | |||||
break; | |||||
size -= messSize; | |||||
data += messSize; | |||||
result.addEvent (mm); | |||||
const uint8 firstByte = *(mm.getRawData()); | |||||
if ((firstByte & 0xf0) != 0xf0) | |||||
lastStatusByte = firstByte; | |||||
} | |||||
// use a sort that puts all the note-offs before note-ons that have the same time | |||||
MidiFileHelpers::Sorter sorter; | |||||
result.list.sort (sorter, true); | |||||
addTrack (result); | |||||
tracks.getLast()->updateMatchedPairs(); | |||||
} | |||||
//============================================================================== | |||||
void MidiFile::convertTimestampTicksToSeconds() | |||||
{ | |||||
MidiMessageSequence tempoEvents; | |||||
findAllTempoEvents (tempoEvents); | |||||
findAllTimeSigEvents (tempoEvents); | |||||
if (timeFormat != 0) | |||||
{ | |||||
for (int i = 0; i < tracks.size(); ++i) | |||||
{ | |||||
const MidiMessageSequence& ms = *tracks.getUnchecked(i); | |||||
for (int j = ms.getNumEvents(); --j >= 0;) | |||||
{ | |||||
MidiMessage& m = ms.getEventPointer(j)->message; | |||||
m.setTimeStamp (MidiFileHelpers::convertTicksToSeconds (m.getTimeStamp(), tempoEvents, timeFormat)); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
bool MidiFile::writeTo (OutputStream& out, int midiFileType) | |||||
{ | |||||
jassert (midiFileType >= 0 && midiFileType <= 2); | |||||
if (! out.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MThd"))) return false; | |||||
if (! out.writeIntBigEndian (6)) return false; | |||||
if (! out.writeShortBigEndian ((short) midiFileType)) return false; | |||||
if (! out.writeShortBigEndian ((short) tracks.size())) return false; | |||||
if (! out.writeShortBigEndian (timeFormat)) return false; | |||||
for (int i = 0; i < tracks.size(); ++i) | |||||
if (! writeTrack (out, i)) | |||||
return false; | |||||
out.flush(); | |||||
return true; | |||||
} | |||||
bool MidiFile::writeTrack (OutputStream& mainOut, const int trackNum) | |||||
{ | |||||
MemoryOutputStream out; | |||||
const MidiMessageSequence& ms = *tracks.getUnchecked (trackNum); | |||||
int lastTick = 0; | |||||
uint8 lastStatusByte = 0; | |||||
bool endOfTrackEventWritten = false; | |||||
for (int i = 0; i < ms.getNumEvents(); ++i) | |||||
{ | |||||
const MidiMessage& mm = ms.getEventPointer(i)->message; | |||||
if (mm.isEndOfTrackMetaEvent()) | |||||
endOfTrackEventWritten = true; | |||||
const int tick = roundToInt (mm.getTimeStamp()); | |||||
const int delta = jmax (0, tick - lastTick); | |||||
MidiFileHelpers::writeVariableLengthInt (out, (uint32) delta); | |||||
lastTick = tick; | |||||
const uint8* data = mm.getRawData(); | |||||
int dataSize = mm.getRawDataSize(); | |||||
const uint8 statusByte = data[0]; | |||||
if (statusByte == lastStatusByte | |||||
&& (statusByte & 0xf0) != 0xf0 | |||||
&& dataSize > 1 | |||||
&& i > 0) | |||||
{ | |||||
++data; | |||||
--dataSize; | |||||
} | |||||
else if (statusByte == 0xf0) // Write sysex message with length bytes. | |||||
{ | |||||
out.writeByte ((char) statusByte); | |||||
++data; | |||||
--dataSize; | |||||
MidiFileHelpers::writeVariableLengthInt (out, (uint32) dataSize); | |||||
} | |||||
out.write (data, (size_t) dataSize); | |||||
lastStatusByte = statusByte; | |||||
} | |||||
if (! endOfTrackEventWritten) | |||||
{ | |||||
out.writeByte (0); // (tick delta) | |||||
const MidiMessage m (MidiMessage::endOfTrack()); | |||||
out.write (m.getRawData(), (size_t) m.getRawDataSize()); | |||||
} | |||||
if (! mainOut.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MTrk"))) return false; | |||||
if (! mainOut.writeIntBigEndian ((int) out.getDataSize())) return false; | |||||
mainOut << out; | |||||
return true; | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,182 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
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(); | |||||
/** Creates a copy of another MidiFile. */ | |||||
MidiFile (const MidiFile& other); | |||||
/** Copies from another MidiFile object */ | |||||
MidiFile& operator= (const MidiFile& other); | |||||
//============================================================================== | |||||
/** Returns the number of tracks in the file. | |||||
@see getTrack, addTrack | |||||
*/ | |||||
int getNumTracks() const noexcept; | |||||
/** Returns a pointer to one of the tracks in the file. | |||||
@returns a pointer to the track, or nullptr if the index is out-of-range | |||||
@see getNumTracks, addTrack | |||||
*/ | |||||
const MidiMessageSequence* getTrack (int index) const noexcept; | |||||
/** Adds a midi track to the file. | |||||
This will make its own internal copy of the sequence that is passed-in. | |||||
@see getNumTracks, getTrack | |||||
*/ | |||||
void addTrack (const MidiMessageSequence& trackSequence); | |||||
/** Removes all midi tracks from the file. | |||||
@see getNumTracks | |||||
*/ | |||||
void clear(); | |||||
/** Returns the raw time format code that will be written to a stream. | |||||
After reading a midi file, this method will return the time-format that | |||||
was read from the file's header. It can be changed using the setTicksPerQuarterNote() | |||||
or setSmpteTimeFormat() methods. | |||||
If the value returned is positive, it indicates the number of midi ticks | |||||
per quarter-note - see setTicksPerQuarterNote(). | |||||
It it's negative, the upper byte indicates the frames-per-second (but negative), and | |||||
the lower byte is the number of ticks per frame - see setSmpteTimeFormat(). | |||||
*/ | |||||
short getTimeFormat() const noexcept; | |||||
/** Sets the time format to use when this file is written to a stream. | |||||
If this is called, the file will be written as bars/beats using the | |||||
specified resolution, rather than SMPTE absolute times, as would be | |||||
used if setSmpteTimeFormat() had been called instead. | |||||
@param ticksPerQuarterNote e.g. 96, 960 | |||||
@see setSmpteTimeFormat | |||||
*/ | |||||
void setTicksPerQuarterNote (int ticksPerQuarterNote) noexcept; | |||||
/** Sets the time format to use when this file is written to a stream. | |||||
If this is called, the file will be written using absolute times, rather | |||||
than bars/beats as would be the case if setTicksPerBeat() had been called | |||||
instead. | |||||
@param framesPerSecond must be 24, 25, 29 or 30 | |||||
@param subframeResolution the sub-second resolution, e.g. 4 (midi time code), | |||||
8, 10, 80 (SMPTE bit resolution), or 100. For millisecond | |||||
timing, setSmpteTimeFormat (25, 40) | |||||
@see setTicksPerBeat | |||||
*/ | |||||
void setSmpteTimeFormat (int framesPerSecond, | |||||
int subframeResolution) noexcept; | |||||
//============================================================================== | |||||
/** Makes a list of all the tempo-change meta-events from all tracks in the midi file. | |||||
Useful for finding the positions of all the tempo changes in a file. | |||||
@param tempoChangeEvents a list to which all the events will be added | |||||
*/ | |||||
void findAllTempoEvents (MidiMessageSequence& tempoChangeEvents) const; | |||||
/** Makes a list of all the time-signature meta-events from all tracks in the midi file. | |||||
Useful for finding the positions of all the tempo changes in a file. | |||||
@param timeSigEvents a list to which all the events will be added | |||||
*/ | |||||
void findAllTimeSigEvents (MidiMessageSequence& timeSigEvents) const; | |||||
/** Makes a list of all the time-signature meta-events from all tracks in the midi file. | |||||
@param keySigEvents a list to which all the events will be added | |||||
*/ | |||||
void findAllKeySigEvents (MidiMessageSequence& keySigEvents) const; | |||||
/** Returns the latest timestamp in any of the tracks. | |||||
(Useful for finding the length of the file). | |||||
*/ | |||||
double getLastTimestamp() const; | |||||
//============================================================================== | |||||
/** Reads a midi file format stream. | |||||
After calling this, you can get the tracks that were read from the file by using the | |||||
getNumTracks() and getTrack() methods. | |||||
The timestamps of the midi events in the tracks will represent their positions in | |||||
terms of midi ticks. To convert them to seconds, use the convertTimestampTicksToSeconds() | |||||
method. | |||||
@returns true if the stream was read successfully | |||||
*/ | |||||
bool readFrom (InputStream& sourceStream); | |||||
/** Writes the midi tracks as a standard midi file. | |||||
The midiFileType value is written as the file's format type, which can be 0, 1 | |||||
or 2 - see the midi file spec for more info about that. | |||||
@returns true if the operation succeeded. | |||||
*/ | |||||
bool writeTo (OutputStream& destStream, int midiFileType = 1); | |||||
/** Converts the timestamp of all the midi events from midi ticks to seconds. | |||||
This will use the midi time format and tempo/time signature info in the | |||||
tracks to convert all the timestamps to absolute values in seconds. | |||||
*/ | |||||
void convertTimestampTicksToSeconds(); | |||||
private: | |||||
//============================================================================== | |||||
OwnedArray<MidiMessageSequence> tracks; | |||||
short timeFormat; | |||||
void readNextTrack (const uint8*, int size); | |||||
bool writeTrack (OutputStream&, int trackNum); | |||||
JUCE_LEAK_DETECTOR (MidiFile) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,186 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
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, 128) | |||||
&& (noteStates[n] & (1 << (midiChannel - 1))) != 0; | |||||
} | |||||
bool MidiKeyboardState::isNoteOnForChannels (const int midiChannelMask, const int n) const noexcept | |||||
{ | |||||
return isPositiveAndBelow (n, 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, 128)); | |||||
const ScopedLock sl (lock); | |||||
if (isPositiveAndBelow (midiNoteNumber, 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, 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 float velocity) | |||||
{ | |||||
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, velocity); | |||||
} | |||||
} | |||||
void MidiKeyboardState::noteOffInternal (const int midiChannel, const int midiNoteNumber, const float velocity) | |||||
{ | |||||
if (isNoteOn (midiChannel, midiNoteNumber)) | |||||
{ | |||||
noteStates [midiNoteNumber] &= ~(1 << (midiChannel - 1)); | |||||
for (int i = listeners.size(); --i >= 0;) | |||||
listeners.getUnchecked(i)->handleNoteOff (this, midiChannel, midiNoteNumber, velocity); | |||||
} | |||||
} | |||||
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, 0.0f); | |||||
} | |||||
} | |||||
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(), message.getFloatVelocity()); | |||||
} | |||||
else if (message.isAllNotesOff()) | |||||
{ | |||||
for (int i = 0; i < 128; ++i) | |||||
noteOffInternal (message.getChannel(), i, 0.0f); | |||||
} | |||||
} | |||||
void MidiKeyboardState::processNextMidiBuffer (MidiBuffer& buffer, | |||||
const int startSample, | |||||
const int numSamples, | |||||
const bool injectIndirectEvents) | |||||
{ | |||||
MidiBuffer::Iterator i (buffer); | |||||
MidiMessage message; | |||||
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); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,202 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
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, float velocity) = 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, float velocity); | |||||
/** 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, float velocity); | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiKeyboardState) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,940 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
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 list of bytes. */ | |||||
template <typename... Data> | |||||
MidiMessage (int byte1, int byte2, int byte3, Data... otherBytes) : size (3 + sizeof... (otherBytes)) | |||||
{ | |||||
// this checks that the length matches the data.. | |||||
jassert (size > 3 || byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == size); | |||||
const uint8 data[] = { (uint8) byte1, (uint8) byte2, (uint8) byte3, static_cast<uint8> (otherBytes)... }; | |||||
memcpy (allocateSpace (size), data, (size_t) size); | |||||
} | |||||
/** Creates a midi message from a block of data. */ | |||||
MidiMessage (const void* data, int numBytes, double timeStamp = 0); | |||||
/** Reads the next midi message from some data. | |||||
This will read as many bytes from a data stream as it needs to make a | |||||
complete message, and will return the number of bytes it used. This lets | |||||
you read a sequence of midi messages from a file or stream. | |||||
@param data the data to read from | |||||
@param maxBytesToUse the maximum number of bytes it's allowed to read | |||||
@param numBytesUsed returns the number of bytes that were actually needed | |||||
@param lastStatusByte in a sequence of midi messages, the initial byte | |||||
can be dropped from a message if it's the same as the | |||||
first byte of the previous message, so this lets you | |||||
supply the byte to use if the first byte of the message | |||||
has in fact been dropped. | |||||
@param timeStamp the time to give the midi message - this value doesn't | |||||
use any particular units, so will be application-specific | |||||
@param sysexHasEmbeddedLength when reading sysexes, this flag indicates whether | |||||
to expect the data to begin with a variable-length field | |||||
indicating its size | |||||
*/ | |||||
MidiMessage (const void* data, int maxBytesToUse, | |||||
int& numBytesUsed, uint8 lastStatusByte, | |||||
double timeStamp = 0, | |||||
bool sysexHasEmbeddedLength = true); | |||||
/** Creates an active-sense message. | |||||
Since the MidiMessage has to contain a valid message, this default constructor | |||||
just initialises it with an empty sysex message. | |||||
*/ | |||||
MidiMessage() noexcept; | |||||
/** Creates a copy of another midi message. */ | |||||
MidiMessage (const MidiMessage&); | |||||
/** Creates a copy of another midi message, with a different timestamp. */ | |||||
MidiMessage (const MidiMessage&, double newTimeStamp); | |||||
/** Destructor. */ | |||||
~MidiMessage() noexcept; | |||||
/** Copies this message from another one. */ | |||||
MidiMessage& operator= (const MidiMessage& other); | |||||
/** Move constructor */ | |||||
MidiMessage (MidiMessage&&) noexcept; | |||||
/** Move assignment operator */ | |||||
MidiMessage& operator= (MidiMessage&&) noexcept; | |||||
//============================================================================== | |||||
/** Returns a pointer to the raw midi data. | |||||
@see getRawDataSize | |||||
*/ | |||||
const uint8* getRawData() const noexcept { return getData(); } | |||||
/** Returns the number of bytes of data in the message. | |||||
@see getRawData | |||||
*/ | |||||
int getRawDataSize() const noexcept { return size; } | |||||
//============================================================================== | |||||
/** Returns a human-readable description of the midi message as a string, | |||||
for example "Note On C#3 Velocity 120 Channel 1". | |||||
*/ | |||||
String getDescription() const; | |||||
//============================================================================== | |||||
/** 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 1.0 | |||||
@see isNoteOff | |||||
*/ | |||||
static MidiMessage noteOff (int channel, int noteNumber, float velocity) 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) 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 | |||||
@see isNoteOff | |||||
*/ | |||||
static MidiMessage noteOff (int channel, int noteNumber) noexcept; | |||||
/** Returns true if this message is a 'key-down' or 'key-up' event. | |||||
@see isNoteOn, isNoteOff | |||||
*/ | |||||
bool isNoteOnOrOff() const noexcept; | |||||
/** Returns the midi note number for note-on and note-off messages. | |||||
If the message isn't a note-on or off, the value returned is undefined. | |||||
@see isNoteOff, getMidiNoteName, getMidiNoteInHertz, setNoteNumber | |||||
*/ | |||||
int getNoteNumber() const noexcept; | |||||
/** Changes the midi note number of a note-on or note-off message. | |||||
If the message isn't a note on or off, this will do nothing. | |||||
*/ | |||||
void setNoteNumber (int newNoteNumber) noexcept; | |||||
//============================================================================== | |||||
/** Returns the velocity of a note-on or note-off message. | |||||
The value returned will be in the range 0 to 127. | |||||
If the message isn't a note-on or off event, it will return 0. | |||||
@see getFloatVelocity | |||||
*/ | |||||
uint8 getVelocity() const noexcept; | |||||
/** Returns the velocity of a note-on or note-off message. | |||||
The value returned will be in the range 0 to 1.0 | |||||
If the message isn't a note-on or off event, it will return 0. | |||||
@see getVelocity, setVelocity | |||||
*/ | |||||
float getFloatVelocity() const noexcept; | |||||
/** Changes the velocity of a note-on or note-off message. | |||||
If the message isn't a note on or off, this will do nothing. | |||||
@param newVelocity the new velocity, in the range 0 to 1.0 | |||||
@see getFloatVelocity, multiplyVelocity | |||||
*/ | |||||
void setVelocity (float newVelocity) noexcept; | |||||
/** Multiplies the velocity of a note-on or note-off message by a given amount. | |||||
If the message isn't a note on or off, this will do nothing. | |||||
@param scaleFactor the value by which to multiply the velocity | |||||
@see setVelocity | |||||
*/ | |||||
void multiplyVelocity (float scaleFactor) noexcept; | |||||
//============================================================================== | |||||
/** Returns true if this message is a 'sustain pedal down' controller message. */ | |||||
bool isSustainPedalOn() const noexcept; | |||||
/** Returns true if this message is a 'sustain pedal up' controller message. */ | |||||
bool isSustainPedalOff() const noexcept; | |||||
/** Returns true if this message is a 'sostenuto pedal down' controller message. */ | |||||
bool isSostenutoPedalOn() const noexcept; | |||||
/** Returns true if this message is a 'sostenuto pedal up' controller message. */ | |||||
bool isSostenutoPedalOff() const noexcept; | |||||
/** Returns true if this message is a 'soft pedal down' controller message. */ | |||||
bool isSoftPedalOn() const noexcept; | |||||
/** Returns true if this message is a 'soft pedal up' controller message. */ | |||||
bool isSoftPedalOff() const noexcept; | |||||
//============================================================================== | |||||
/** Returns true if the message is a program (patch) change message. | |||||
@see getProgramChangeNumber, getGMInstrumentName | |||||
*/ | |||||
bool isProgramChange() const noexcept; | |||||
/** Returns the new program number of a program change message. | |||||
If the message isn't a program change, the value returned is undefined. | |||||
@see isProgramChange, getGMInstrumentName | |||||
*/ | |||||
int getProgramChangeNumber() const noexcept; | |||||
/** Creates a program-change message. | |||||
@param channel the midi channel, in the range 1 to 16 | |||||
@param programNumber the midi program number, 0 to 127 | |||||
@see isProgramChange, getGMInstrumentName | |||||
*/ | |||||
static MidiMessage programChange (int channel, int programNumber) noexcept; | |||||
//============================================================================== | |||||
/** Returns true if the message is a pitch-wheel move. | |||||
@see getPitchWheelValue, pitchWheel | |||||
*/ | |||||
bool isPitchWheel() const noexcept; | |||||
/** Returns the pitch wheel position from a pitch-wheel move message. | |||||
The value returned is a 14-bit number from 0 to 0x3fff, indicating the wheel position. | |||||
If called for messages which aren't pitch wheel events, the number returned will be | |||||
nonsense. | |||||
@see isPitchWheel | |||||
*/ | |||||
int getPitchWheelValue() const noexcept; | |||||
/** Creates a pitch-wheel move message. | |||||
@param channel the midi channel, in the range 1 to 16 | |||||
@param position the wheel position, in the range 0 to 16383 | |||||
@see isPitchWheel | |||||
*/ | |||||
static MidiMessage pitchWheel (int channel, int position) noexcept; | |||||
//============================================================================== | |||||
/** Returns true if the message is an aftertouch event. | |||||
For aftertouch events, use the getNoteNumber() method to find out the key | |||||
that it applies to, and getAftertouchValue() to find out the amount. Use | |||||
getChannel() to find out the channel. | |||||
@see getAftertouchValue, getNoteNumber | |||||
*/ | |||||
bool isAftertouch() const noexcept; | |||||
/** Returns the amount of aftertouch from an aftertouch messages. | |||||
The value returned is in the range 0 to 127, and will be nonsense for messages | |||||
other than aftertouch messages. | |||||
@see isAftertouch | |||||
*/ | |||||
int getAfterTouchValue() const noexcept; | |||||
/** Creates an aftertouch message. | |||||
@param channel the midi channel, in the range 1 to 16 | |||||
@param noteNumber the key number, 0 to 127 | |||||
@param aftertouchAmount the amount of aftertouch, 0 to 127 | |||||
@see isAftertouch | |||||
*/ | |||||
static MidiMessage aftertouchChange (int channel, | |||||
int noteNumber, | |||||
int aftertouchAmount) noexcept; | |||||
/** Returns true if the message is a channel-pressure change event. | |||||
This is like aftertouch, but common to the whole channel rather than a specific | |||||
note. Use getChannelPressureValue() to find out the pressure, and getChannel() | |||||
to find out the channel. | |||||
@see channelPressureChange | |||||
*/ | |||||
bool isChannelPressure() const noexcept; | |||||
/** Returns the pressure from a channel pressure change message. | |||||
@returns the pressure, in the range 0 to 127 | |||||
@see isChannelPressure, channelPressureChange | |||||
*/ | |||||
int getChannelPressureValue() const noexcept; | |||||
/** Creates a channel-pressure change event. | |||||
@param channel the midi channel: 1 to 16 | |||||
@param pressure the pressure, 0 to 127 | |||||
@see isChannelPressure | |||||
*/ | |||||
static MidiMessage channelPressureChange (int channel, int pressure) noexcept; | |||||
//============================================================================== | |||||
/** Returns true if this is a midi controller message. | |||||
@see getControllerNumber, getControllerValue, controllerEvent | |||||
*/ | |||||
bool isController() const noexcept; | |||||
/** Returns the controller number of a controller message. | |||||
The name of the controller can be looked up using the getControllerName() method. | |||||
Note that the value returned is invalid for messages that aren't controller changes. | |||||
@see isController, getControllerName, getControllerValue | |||||
*/ | |||||
int getControllerNumber() const noexcept; | |||||
/** Returns the controller value from a controller message. | |||||
A value 0 to 127 is returned to indicate the new controller position. | |||||
Note that the value returned is invalid for messages that aren't controller changes. | |||||
@see isController, getControllerNumber | |||||
*/ | |||||
int getControllerValue() const noexcept; | |||||
/** Returns true if this message is a controller message and if it has the specified | |||||
controller type. | |||||
*/ | |||||
bool isControllerOfType (int controllerType) const noexcept; | |||||
/** Creates a controller message. | |||||
@param channel the midi channel, in the range 1 to 16 | |||||
@param controllerType the type of controller | |||||
@param value the controller value | |||||
@see isController | |||||
*/ | |||||
static MidiMessage controllerEvent (int channel, | |||||
int controllerType, | |||||
int value) noexcept; | |||||
/** Checks whether this message is an all-notes-off message. | |||||
@see allNotesOff | |||||
*/ | |||||
bool isAllNotesOff() const noexcept; | |||||
/** Checks whether this message is an all-sound-off message. | |||||
@see allSoundOff | |||||
*/ | |||||
bool isAllSoundOff() const noexcept; | |||||
/** Creates an all-notes-off message. | |||||
@param channel the midi channel, in the range 1 to 16 | |||||
@see isAllNotesOff | |||||
*/ | |||||
static MidiMessage allNotesOff (int channel) noexcept; | |||||
/** Creates an all-sound-off message. | |||||
@param channel the midi channel, in the range 1 to 16 | |||||
@see isAllSoundOff | |||||
*/ | |||||
static MidiMessage allSoundOff (int channel) noexcept; | |||||
/** Creates an all-controllers-off message. | |||||
@param channel the midi channel, in the range 1 to 16 | |||||
*/ | |||||
static MidiMessage allControllersOff (int channel) noexcept; | |||||
//============================================================================== | |||||
/** Returns true if this event is a meta-event. | |||||
Meta-events are things like tempo changes, track names, etc. | |||||
@see getMetaEventType, isTrackMetaEvent, isEndOfTrackMetaEvent, | |||||
isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent, | |||||
isKeySignatureMetaEvent, isMidiChannelMetaEvent | |||||
*/ | |||||
bool isMetaEvent() const noexcept; | |||||
/** Returns a meta-event's type number. | |||||
If the message isn't a meta-event, this will return -1. | |||||
@see isMetaEvent, isTrackMetaEvent, isEndOfTrackMetaEvent, | |||||
isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent, | |||||
isKeySignatureMetaEvent, isMidiChannelMetaEvent | |||||
*/ | |||||
int getMetaEventType() const noexcept; | |||||
/** Returns a pointer to the data in a meta-event. | |||||
@see isMetaEvent, getMetaEventLength | |||||
*/ | |||||
const uint8* getMetaEventData() const noexcept; | |||||
/** Returns the length of the data for a meta-event. | |||||
@see isMetaEvent, getMetaEventData | |||||
*/ | |||||
int getMetaEventLength() const noexcept; | |||||
//============================================================================== | |||||
/** Returns true if this is a 'track' meta-event. */ | |||||
bool isTrackMetaEvent() const noexcept; | |||||
/** Returns true if this is an 'end-of-track' meta-event. */ | |||||
bool isEndOfTrackMetaEvent() const noexcept; | |||||
/** Creates an end-of-track meta-event. | |||||
@see isEndOfTrackMetaEvent | |||||
*/ | |||||
static MidiMessage endOfTrack() noexcept; | |||||
/** Returns true if this is an 'track name' meta-event. | |||||
You can use the getTextFromTextMetaEvent() method to get the track's name. | |||||
*/ | |||||
bool isTrackNameEvent() const noexcept; | |||||
/** Returns true if this is a 'text' meta-event. | |||||
@see getTextFromTextMetaEvent | |||||
*/ | |||||
bool isTextMetaEvent() const noexcept; | |||||
/** Returns the text from a text meta-event. | |||||
@see isTextMetaEvent | |||||
*/ | |||||
String getTextFromTextMetaEvent() const; | |||||
/** Creates a text meta-event. */ | |||||
static MidiMessage textMetaEvent (int type, StringRef text); | |||||
//============================================================================== | |||||
/** Returns true if this is a 'tempo' meta-event. | |||||
@see getTempoMetaEventTickLength, getTempoSecondsPerQuarterNote | |||||
*/ | |||||
bool isTempoMetaEvent() const noexcept; | |||||
/** Returns the tick length from a tempo meta-event. | |||||
@param timeFormat the 16-bit time format value from the midi file's header. | |||||
@returns the tick length (in seconds). | |||||
@see isTempoMetaEvent | |||||
*/ | |||||
double getTempoMetaEventTickLength (short timeFormat) const noexcept; | |||||
/** Calculates the seconds-per-quarter-note from a tempo meta-event. | |||||
@see isTempoMetaEvent, getTempoMetaEventTickLength | |||||
*/ | |||||
double getTempoSecondsPerQuarterNote() const noexcept; | |||||
/** Creates a tempo meta-event. | |||||
@see isTempoMetaEvent | |||||
*/ | |||||
static MidiMessage tempoMetaEvent (int microsecondsPerQuarterNote) noexcept; | |||||
//============================================================================== | |||||
/** Returns true if this is a 'time-signature' meta-event. | |||||
@see getTimeSignatureInfo | |||||
*/ | |||||
bool isTimeSignatureMetaEvent() const noexcept; | |||||
/** Returns the time-signature values from a time-signature meta-event. | |||||
@see isTimeSignatureMetaEvent | |||||
*/ | |||||
void getTimeSignatureInfo (int& numerator, int& denominator) const noexcept; | |||||
/** Creates a time-signature meta-event. | |||||
@see isTimeSignatureMetaEvent | |||||
*/ | |||||
static MidiMessage timeSignatureMetaEvent (int numerator, int denominator); | |||||
//============================================================================== | |||||
/** Returns true if this is a 'key-signature' meta-event. | |||||
@see getKeySignatureNumberOfSharpsOrFlats, isKeySignatureMajorKey | |||||
*/ | |||||
bool isKeySignatureMetaEvent() const noexcept; | |||||
/** Returns the key from a key-signature meta-event. | |||||
This method must only be called if isKeySignatureMetaEvent() is true. | |||||
A positive number here indicates the number of sharps in the key signature, | |||||
and a negative number indicates a number of flats. So e.g. 3 = F# + C# + G#, | |||||
-2 = Bb + Eb | |||||
@see isKeySignatureMetaEvent, isKeySignatureMajorKey | |||||
*/ | |||||
int getKeySignatureNumberOfSharpsOrFlats() const noexcept; | |||||
/** Returns true if this key-signature event is major, or false if it's minor. | |||||
This method must only be called if isKeySignatureMetaEvent() is true. | |||||
*/ | |||||
bool isKeySignatureMajorKey() const noexcept; | |||||
/** Creates a key-signature meta-event. | |||||
@param numberOfSharpsOrFlats if positive, this indicates the number of sharps | |||||
in the key; if negative, the number of flats | |||||
@param isMinorKey if true, the key is minor; if false, it is major | |||||
@see isKeySignatureMetaEvent | |||||
*/ | |||||
static MidiMessage keySignatureMetaEvent (int numberOfSharpsOrFlats, bool isMinorKey); | |||||
//============================================================================== | |||||
/** Returns true if this is a 'channel' meta-event. | |||||
A channel meta-event specifies the midi channel that should be used | |||||
for subsequent meta-events. | |||||
@see getMidiChannelMetaEventChannel | |||||
*/ | |||||
bool isMidiChannelMetaEvent() const noexcept; | |||||
/** Returns the channel number from a channel meta-event. | |||||
@returns the channel, in the range 1 to 16. | |||||
@see isMidiChannelMetaEvent | |||||
*/ | |||||
int getMidiChannelMetaEventChannel() const noexcept; | |||||
/** Creates a midi channel meta-event. | |||||
@param channel the midi channel, in the range 1 to 16 | |||||
@see isMidiChannelMetaEvent | |||||
*/ | |||||
static MidiMessage midiChannelMetaEvent (int channel) noexcept; | |||||
//============================================================================== | |||||
/** Returns true if this is an active-sense message. */ | |||||
bool isActiveSense() const noexcept; | |||||
//============================================================================== | |||||
/** Returns true if this is a midi start event. | |||||
@see midiStart | |||||
*/ | |||||
bool isMidiStart() const noexcept; | |||||
/** Creates a midi start event. */ | |||||
static MidiMessage midiStart() noexcept; | |||||
/** Returns true if this is a midi continue event. | |||||
@see midiContinue | |||||
*/ | |||||
bool isMidiContinue() const noexcept; | |||||
/** Creates a midi continue event. */ | |||||
static MidiMessage midiContinue() noexcept; | |||||
/** Returns true if this is a midi stop event. | |||||
@see midiStop | |||||
*/ | |||||
bool isMidiStop() const noexcept; | |||||
/** Creates a midi stop event. */ | |||||
static MidiMessage midiStop() noexcept; | |||||
/** Returns true if this is a midi clock event. | |||||
@see midiClock, songPositionPointer | |||||
*/ | |||||
bool isMidiClock() const noexcept; | |||||
/** Creates a midi clock event. */ | |||||
static MidiMessage midiClock() noexcept; | |||||
/** Returns true if this is a song-position-pointer message. | |||||
@see getSongPositionPointerMidiBeat, songPositionPointer | |||||
*/ | |||||
bool isSongPositionPointer() const noexcept; | |||||
/** Returns the midi beat-number of a song-position-pointer message. | |||||
@see isSongPositionPointer, songPositionPointer | |||||
*/ | |||||
int getSongPositionPointerMidiBeat() const noexcept; | |||||
/** Creates a song-position-pointer message. | |||||
The position is a number of midi beats from the start of the song, where 1 midi | |||||
beat is 6 midi clocks, and there are 24 midi clocks in a quarter-note. So there | |||||
are 4 midi beats in a quarter-note. | |||||
@see isSongPositionPointer, getSongPositionPointerMidiBeat | |||||
*/ | |||||
static MidiMessage songPositionPointer (int positionInMidiBeats) noexcept; | |||||
//============================================================================== | |||||
/** Returns true if this is a quarter-frame midi timecode message. | |||||
@see quarterFrame, getQuarterFrameSequenceNumber, getQuarterFrameValue | |||||
*/ | |||||
bool isQuarterFrame() const noexcept; | |||||
/** Returns the sequence number of a quarter-frame midi timecode message. | |||||
This will be a value between 0 and 7. | |||||
@see isQuarterFrame, getQuarterFrameValue, quarterFrame | |||||
*/ | |||||
int getQuarterFrameSequenceNumber() const noexcept; | |||||
/** Returns the value from a quarter-frame message. | |||||
This will be the lower nybble of the message's data-byte, a value between 0 and 15 | |||||
*/ | |||||
int getQuarterFrameValue() const noexcept; | |||||
/** Creates a quarter-frame MTC message. | |||||
@param sequenceNumber a value 0 to 7 for the upper nybble of the message's data byte | |||||
@param value a value 0 to 15 for the lower nybble of the message's data byte | |||||
*/ | |||||
static MidiMessage quarterFrame (int sequenceNumber, int value) noexcept; | |||||
/** SMPTE timecode types. | |||||
Used by the getFullFrameParameters() and fullFrame() methods. | |||||
*/ | |||||
enum SmpteTimecodeType | |||||
{ | |||||
fps24 = 0, | |||||
fps25 = 1, | |||||
fps30drop = 2, | |||||
fps30 = 3 | |||||
}; | |||||
/** Returns true if this is a full-frame midi timecode message. */ | |||||
bool isFullFrame() const noexcept; | |||||
/** Extracts the timecode information from a full-frame midi timecode message. | |||||
You should only call this on messages where you've used isFullFrame() to | |||||
check that they're the right kind. | |||||
*/ | |||||
void getFullFrameParameters (int& hours, | |||||
int& minutes, | |||||
int& seconds, | |||||
int& frames, | |||||
SmpteTimecodeType& timecodeType) const noexcept; | |||||
/** Creates a full-frame MTC message. */ | |||||
static MidiMessage fullFrame (int hours, | |||||
int minutes, | |||||
int seconds, | |||||
int frames, | |||||
SmpteTimecodeType timecodeType); | |||||
//============================================================================== | |||||
/** Types of MMC command. | |||||
@see isMidiMachineControlMessage, getMidiMachineControlCommand, midiMachineControlCommand | |||||
*/ | |||||
enum MidiMachineControlCommand | |||||
{ | |||||
mmc_stop = 1, | |||||
mmc_play = 2, | |||||
mmc_deferredplay = 3, | |||||
mmc_fastforward = 4, | |||||
mmc_rewind = 5, | |||||
mmc_recordStart = 6, | |||||
mmc_recordStop = 7, | |||||
mmc_pause = 9 | |||||
}; | |||||
/** Checks whether this is an MMC message. | |||||
If it is, you can use the getMidiMachineControlCommand() to find out its type. | |||||
*/ | |||||
bool isMidiMachineControlMessage() const noexcept; | |||||
/** For an MMC message, this returns its type. | |||||
Make sure it's actually an MMC message with isMidiMachineControlMessage() before | |||||
calling this method. | |||||
*/ | |||||
MidiMachineControlCommand getMidiMachineControlCommand() const noexcept; | |||||
/** Creates an MMC message. */ | |||||
static MidiMessage midiMachineControlCommand (MidiMachineControlCommand command); | |||||
/** Checks whether this is an MMC "goto" message. | |||||
If it is, the parameters passed-in are set to the time that the message contains. | |||||
@see midiMachineControlGoto | |||||
*/ | |||||
bool isMidiMachineControlGoto (int& hours, | |||||
int& minutes, | |||||
int& seconds, | |||||
int& frames) const noexcept; | |||||
/** Creates an MMC "goto" message. | |||||
This messages tells the device to go to a specific frame. | |||||
@see isMidiMachineControlGoto | |||||
*/ | |||||
static MidiMessage midiMachineControlGoto (int hours, | |||||
int minutes, | |||||
int seconds, | |||||
int frames); | |||||
//============================================================================== | |||||
/** Creates a master-volume change message. | |||||
@param volume the volume, 0 to 1.0 | |||||
*/ | |||||
static MidiMessage masterVolume (float volume); | |||||
//============================================================================== | |||||
/** Creates a system-exclusive message. | |||||
The data passed in is wrapped with header and tail bytes of 0xf0 and 0xf7. | |||||
*/ | |||||
static MidiMessage createSysExMessage (const void* sysexData, | |||||
int dataSize); | |||||
//============================================================================== | |||||
/** Reads a midi variable-length integer. | |||||
@param data the data to read the number from | |||||
@param numBytesUsed on return, this will be set to the number of bytes that were read | |||||
*/ | |||||
static int readVariableLengthVal (const uint8* data, | |||||
int& numBytesUsed) noexcept; | |||||
/** Based on the first byte of a short midi message, this uses a lookup table | |||||
to return the message length (either 1, 2, or 3 bytes). | |||||
The value passed in must be 0x80 or higher. | |||||
*/ | |||||
static int getMessageLengthFromFirstByte (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, double frequencyOfA = 440.0) noexcept; | |||||
/** Returns true if the given midi note number is a black key. */ | |||||
static bool isMidiNoteBlack (int noteNumber) noexcept; | |||||
/** Returns the standard name of a GM instrument, or nullptr if unknown for this index. | |||||
@param midiInstrumentNumber the program number 0 to 127 | |||||
@see getProgramChangeNumber | |||||
*/ | |||||
static const char* getGMInstrumentName (int midiInstrumentNumber); | |||||
/** Returns the name of a bank of GM instruments, or nullptr if unknown for this bank number. | |||||
@param midiBankNumber the bank, 0 to 15 | |||||
*/ | |||||
static const char* getGMInstrumentBankName (int midiBankNumber); | |||||
/** Returns the standard name of a channel 10 percussion sound, or nullptr if unknown for this note number. | |||||
@param midiNoteNumber the key number, 35 to 81 | |||||
*/ | |||||
static const char* getRhythmInstrumentName (int midiNoteNumber); | |||||
/** Returns the name of a controller type number, or nullptr if unknown for this controller number. | |||||
@see getControllerNumber | |||||
*/ | |||||
static const char* getControllerName (int controllerNumber); | |||||
/** Converts a floating-point value between 0 and 1 to a MIDI 7-bit value between 0 and 127. */ | |||||
static uint8 floatValueToMidiByte (float valueBetween0and1) noexcept; | |||||
/** Converts a pitchbend value in semitones to a MIDI 14-bit pitchwheel position value. */ | |||||
static uint16 pitchbendToPitchwheelPos (float pitchbendInSemitones, | |||||
float pitchbendRangeInSemitones) noexcept; | |||||
private: | |||||
//============================================================================== | |||||
#ifndef DOXYGEN | |||||
union PackedData | |||||
{ | |||||
uint8* allocatedData; | |||||
uint8 asBytes[sizeof (uint8*)]; | |||||
}; | |||||
PackedData packedData; | |||||
double timeStamp = 0; | |||||
int size; | |||||
#endif | |||||
inline bool isHeapAllocated() const noexcept { return size > (int) sizeof (packedData); } | |||||
inline uint8* getData() const noexcept { return isHeapAllocated() ? packedData.allocatedData : (uint8*) packedData.asBytes; } | |||||
uint8* allocateSpace (int); | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,340 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
MidiMessageSequence::MidiEventHolder::MidiEventHolder (const MidiMessage& mm) : message (mm) {} | |||||
MidiMessageSequence::MidiEventHolder::MidiEventHolder (MidiMessage&& mm) : message (static_cast<MidiMessage&&> (mm)) {} | |||||
MidiMessageSequence::MidiEventHolder::~MidiEventHolder() {} | |||||
//============================================================================== | |||||
MidiMessageSequence::MidiMessageSequence() | |||||
{ | |||||
} | |||||
MidiMessageSequence::MidiMessageSequence (const MidiMessageSequence& other) | |||||
{ | |||||
list.addCopiesOf (other.list); | |||||
updateMatchedPairs(); | |||||
} | |||||
MidiMessageSequence& MidiMessageSequence::operator= (const MidiMessageSequence& other) | |||||
{ | |||||
MidiMessageSequence otherCopy (other); | |||||
swapWith (otherCopy); | |||||
return *this; | |||||
} | |||||
MidiMessageSequence::MidiMessageSequence (MidiMessageSequence&& other) noexcept | |||||
: list (static_cast<OwnedArray<MidiEventHolder>&&> (other.list)) | |||||
{} | |||||
MidiMessageSequence& MidiMessageSequence::operator= (MidiMessageSequence&& other) noexcept | |||||
{ | |||||
list = static_cast<OwnedArray<MidiEventHolder>&&> (other.list); | |||||
return *this; | |||||
} | |||||
MidiMessageSequence::~MidiMessageSequence() | |||||
{ | |||||
} | |||||
void MidiMessageSequence::swapWith (MidiMessageSequence& other) noexcept | |||||
{ | |||||
list.swapWith (other.list); | |||||
} | |||||
void MidiMessageSequence::clear() | |||||
{ | |||||
list.clear(); | |||||
} | |||||
int MidiMessageSequence::getNumEvents() const noexcept | |||||
{ | |||||
return list.size(); | |||||
} | |||||
MidiMessageSequence::MidiEventHolder* MidiMessageSequence::getEventPointer (int index) const noexcept | |||||
{ | |||||
return list[index]; | |||||
} | |||||
MidiMessageSequence::MidiEventHolder** MidiMessageSequence::begin() const noexcept { return list.begin(); } | |||||
MidiMessageSequence::MidiEventHolder** MidiMessageSequence::end() const noexcept { return list.end(); } | |||||
double MidiMessageSequence::getTimeOfMatchingKeyUp (int index) const noexcept | |||||
{ | |||||
if (auto* meh = list[index]) | |||||
if (meh->noteOffObject != nullptr) | |||||
return meh->noteOffObject->message.getTimeStamp(); | |||||
return 0.0; | |||||
} | |||||
int MidiMessageSequence::getIndexOfMatchingKeyUp (int index) const noexcept | |||||
{ | |||||
if (auto* meh = list [index]) | |||||
return list.indexOf (meh->noteOffObject); | |||||
return -1; | |||||
} | |||||
int MidiMessageSequence::getIndexOf (const MidiEventHolder* event) const noexcept | |||||
{ | |||||
return list.indexOf (event); | |||||
} | |||||
int MidiMessageSequence::getNextIndexAtTime (double timeStamp) const noexcept | |||||
{ | |||||
const int numEvents = list.size(); | |||||
int i; | |||||
for (i = 0; i < numEvents; ++i) | |||||
if (list.getUnchecked(i)->message.getTimeStamp() >= timeStamp) | |||||
break; | |||||
return i; | |||||
} | |||||
//============================================================================== | |||||
double MidiMessageSequence::getStartTime() const noexcept | |||||
{ | |||||
return getEventTime (0); | |||||
} | |||||
double MidiMessageSequence::getEndTime() const noexcept | |||||
{ | |||||
return getEventTime (list.size() - 1); | |||||
} | |||||
double MidiMessageSequence::getEventTime (const int index) const noexcept | |||||
{ | |||||
if (auto* meh = list [index]) | |||||
return meh->message.getTimeStamp(); | |||||
return 0.0; | |||||
} | |||||
//============================================================================== | |||||
MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (MidiEventHolder* newEvent, double timeAdjustment) | |||||
{ | |||||
newEvent->message.addToTimeStamp (timeAdjustment); | |||||
auto time = newEvent->message.getTimeStamp(); | |||||
int i; | |||||
for (i = list.size(); --i >= 0;) | |||||
if (list.getUnchecked(i)->message.getTimeStamp() <= time) | |||||
break; | |||||
list.insert (i + 1, newEvent); | |||||
return newEvent; | |||||
} | |||||
MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (const MidiMessage& newMessage, double timeAdjustment) | |||||
{ | |||||
return addEvent (new MidiEventHolder (newMessage), timeAdjustment); | |||||
} | |||||
MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (MidiMessage&& newMessage, double timeAdjustment) | |||||
{ | |||||
return addEvent (new MidiEventHolder (static_cast<MidiMessage&&> (newMessage)), timeAdjustment); | |||||
} | |||||
void MidiMessageSequence::deleteEvent (int index, bool deleteMatchingNoteUp) | |||||
{ | |||||
if (isPositiveAndBelow (index, list.size())) | |||||
{ | |||||
if (deleteMatchingNoteUp) | |||||
deleteEvent (getIndexOfMatchingKeyUp (index), false); | |||||
list.remove (index); | |||||
} | |||||
} | |||||
void MidiMessageSequence::addSequence (const MidiMessageSequence& other, double timeAdjustment) | |||||
{ | |||||
for (auto* m : other) | |||||
{ | |||||
auto newOne = new MidiEventHolder (m->message); | |||||
newOne->message.addToTimeStamp (timeAdjustment); | |||||
list.add (newOne); | |||||
} | |||||
sort(); | |||||
} | |||||
void MidiMessageSequence::addSequence (const MidiMessageSequence& other, | |||||
double timeAdjustment, | |||||
double firstAllowableTime, | |||||
double endOfAllowableDestTimes) | |||||
{ | |||||
for (auto* m : other) | |||||
{ | |||||
auto t = m->message.getTimeStamp() + timeAdjustment; | |||||
if (t >= firstAllowableTime && t < endOfAllowableDestTimes) | |||||
{ | |||||
auto newOne = new MidiEventHolder (m->message); | |||||
newOne->message.setTimeStamp (t); | |||||
list.add (newOne); | |||||
} | |||||
} | |||||
sort(); | |||||
} | |||||
struct MidiMessageSequenceSorter | |||||
{ | |||||
static int compareElements (const MidiMessageSequence::MidiEventHolder* first, | |||||
const MidiMessageSequence::MidiEventHolder* second) noexcept | |||||
{ | |||||
auto diff = first->message.getTimeStamp() - second->message.getTimeStamp(); | |||||
return (diff > 0) - (diff < 0); | |||||
} | |||||
}; | |||||
void MidiMessageSequence::sort() noexcept | |||||
{ | |||||
MidiMessageSequenceSorter sorter; | |||||
list.sort (sorter, true); | |||||
} | |||||
void MidiMessageSequence::updateMatchedPairs() noexcept | |||||
{ | |||||
for (int i = 0; i < list.size(); ++i) | |||||
{ | |||||
auto* meh = list.getUnchecked(i); | |||||
auto& m1 = meh->message; | |||||
if (m1.isNoteOn()) | |||||
{ | |||||
meh->noteOffObject = nullptr; | |||||
auto note = m1.getNoteNumber(); | |||||
auto chan = m1.getChannel(); | |||||
auto len = list.size(); | |||||
for (int j = i + 1; j < len; ++j) | |||||
{ | |||||
auto* meh2 = list.getUnchecked(j); | |||||
auto& m = meh2->message; | |||||
if (m.getNoteNumber() == note && m.getChannel() == chan) | |||||
{ | |||||
if (m.isNoteOff()) | |||||
{ | |||||
meh->noteOffObject = meh2; | |||||
break; | |||||
} | |||||
if (m.isNoteOn()) | |||||
{ | |||||
auto newEvent = new MidiEventHolder (MidiMessage::noteOff (chan, note)); | |||||
list.insert (j, newEvent); | |||||
newEvent->message.setTimeStamp (m.getTimeStamp()); | |||||
meh->noteOffObject = newEvent; | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
void MidiMessageSequence::addTimeToMessages (double delta) noexcept | |||||
{ | |||||
if (delta != 0) | |||||
for (auto* m : list) | |||||
m->message.addToTimeStamp (delta); | |||||
} | |||||
//============================================================================== | |||||
void MidiMessageSequence::extractMidiChannelMessages (const int channelNumberToExtract, | |||||
MidiMessageSequence& destSequence, | |||||
const bool alsoIncludeMetaEvents) const | |||||
{ | |||||
for (auto* meh : list) | |||||
if (meh->message.isForChannel (channelNumberToExtract) | |||||
|| (alsoIncludeMetaEvents && meh->message.isMetaEvent())) | |||||
destSequence.addEvent (meh->message); | |||||
} | |||||
void MidiMessageSequence::extractSysExMessages (MidiMessageSequence& destSequence) const | |||||
{ | |||||
for (auto* meh : list) | |||||
if (meh->message.isSysEx()) | |||||
destSequence.addEvent (meh->message); | |||||
} | |||||
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 (int channelNumber, double time, Array<MidiMessage>& dest) | |||||
{ | |||||
bool doneProg = false; | |||||
bool donePitchWheel = false; | |||||
bool doneControllers[128] = {}; | |||||
for (int i = list.size(); --i >= 0;) | |||||
{ | |||||
auto& mm = list.getUnchecked(i)->message; | |||||
if (mm.isForChannel (channelNumber) && mm.getTimeStamp() <= time) | |||||
{ | |||||
if (mm.isProgramChange() && ! doneProg) | |||||
{ | |||||
doneProg = true; | |||||
dest.add (MidiMessage (mm, 0.0)); | |||||
} | |||||
else if (mm.isPitchWheel() && ! donePitchWheel) | |||||
{ | |||||
donePitchWheel = true; | |||||
dest.add (MidiMessage (mm, 0.0)); | |||||
} | |||||
else if (mm.isController()) | |||||
{ | |||||
const int controllerNumber = mm.getControllerNumber(); | |||||
jassert (isPositiveAndBelow (controllerNumber, 128)); | |||||
if (! doneControllers[controllerNumber]) | |||||
{ | |||||
doneControllers[controllerNumber] = true; | |||||
dest.add (MidiMessage (mm, 0.0)); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,298 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
A sequence of timestamped midi messages. | |||||
This allows the sequence to be manipulated, and also to be read from and | |||||
written to a standard midi file. | |||||
@see MidiMessage, MidiFile | |||||
*/ | |||||
class JUCE_API MidiMessageSequence | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an empty midi sequence object. */ | |||||
MidiMessageSequence(); | |||||
/** Creates a copy of another sequence. */ | |||||
MidiMessageSequence (const MidiMessageSequence&); | |||||
/** Replaces this sequence with another one. */ | |||||
MidiMessageSequence& operator= (const MidiMessageSequence&); | |||||
/** Move constructor */ | |||||
MidiMessageSequence (MidiMessageSequence&&) noexcept; | |||||
/** Move assignment operator */ | |||||
MidiMessageSequence& operator= (MidiMessageSequence&&) noexcept; | |||||
/** Destructor. */ | |||||
~MidiMessageSequence(); | |||||
//============================================================================== | |||||
/** Structure used to hold midi events in the sequence. | |||||
These structures act as 'handles' on the events as they are moved about in | |||||
the list, and make it quick to find the matching note-offs for note-on events. | |||||
@see MidiMessageSequence::getEventPointer | |||||
*/ | |||||
class MidiEventHolder | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Destructor. */ | |||||
~MidiEventHolder(); | |||||
/** The message itself, whose timestamp is used to specify the event's time. */ | |||||
MidiMessage message; | |||||
/** The matching note-off event (if this is a note-on event). | |||||
If this isn't a note-on, this pointer will be nullptr. | |||||
Use the MidiMessageSequence::updateMatchedPairs() method to keep these | |||||
note-offs up-to-date after events have been moved around in the sequence | |||||
or deleted. | |||||
*/ | |||||
MidiEventHolder* noteOffObject = nullptr; | |||||
private: | |||||
//============================================================================== | |||||
friend class MidiMessageSequence; | |||||
MidiEventHolder (const MidiMessage&); | |||||
MidiEventHolder (MidiMessage&&); | |||||
JUCE_LEAK_DETECTOR (MidiEventHolder) | |||||
}; | |||||
//============================================================================== | |||||
/** Clears the sequence. */ | |||||
void clear(); | |||||
/** Returns the number of events in the sequence. */ | |||||
int getNumEvents() const noexcept; | |||||
/** Returns a pointer to one of the events. */ | |||||
MidiEventHolder* getEventPointer (int index) const noexcept; | |||||
/** Iterator for the list of MidiEventHolders */ | |||||
MidiEventHolder** begin() const noexcept; | |||||
/** Iterator for the list of MidiEventHolders */ | |||||
MidiEventHolder** end() const noexcept; | |||||
/** Returns the time of the note-up that matches the note-on at this index. | |||||
If the event at this index isn't a note-on, it'll just return 0. | |||||
@see MidiMessageSequence::MidiEventHolder::noteOffObject | |||||
*/ | |||||
double getTimeOfMatchingKeyUp (int index) const noexcept; | |||||
/** Returns the index of the note-up that matches the note-on at this index. | |||||
If the event at this index isn't a note-on, it'll just return -1. | |||||
@see MidiMessageSequence::MidiEventHolder::noteOffObject | |||||
*/ | |||||
int getIndexOfMatchingKeyUp (int index) const noexcept; | |||||
/** Returns the index of an event. */ | |||||
int getIndexOf (const MidiEventHolder* event) const noexcept; | |||||
/** Returns the index of the first event on or after the given timestamp. | |||||
If the time is beyond the end of the sequence, this will return the | |||||
number of events. | |||||
*/ | |||||
int getNextIndexAtTime (double timeStamp) const noexcept; | |||||
//============================================================================== | |||||
/** Returns the timestamp of the first event in the sequence. | |||||
@see getEndTime | |||||
*/ | |||||
double getStartTime() const noexcept; | |||||
/** Returns the timestamp of the last event in the sequence. | |||||
@see getStartTime | |||||
*/ | |||||
double getEndTime() const noexcept; | |||||
/** Returns the timestamp of the event at a given index. | |||||
If the index is out-of-range, this will return 0.0 | |||||
*/ | |||||
double getEventTime (int index) const noexcept; | |||||
//============================================================================== | |||||
/** Inserts a midi message into the sequence. | |||||
The index at which the new message gets inserted will depend on its timestamp, | |||||
because the sequence is kept sorted. | |||||
Remember to call updateMatchedPairs() after adding note-on events. | |||||
@param newMessage the new message to add (an internal copy will be made) | |||||
@param timeAdjustment an optional value to add to the timestamp of the message | |||||
that will be inserted | |||||
@see updateMatchedPairs | |||||
*/ | |||||
MidiEventHolder* addEvent (const MidiMessage& newMessage, double timeAdjustment = 0); | |||||
/** 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 (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); | |||||
/** 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 | |||||
*/ | |||||
void addSequence (const MidiMessageSequence& other, | |||||
double timeAdjustmentDelta); | |||||
//============================================================================== | |||||
/** Makes sure all the note-on and note-off pairs are up-to-date. | |||||
Call this after re-ordering messages or deleting/adding messages, and it | |||||
will scan the list and make sure all the note-offs in the MidiEventHolder | |||||
structures are pointing at the correct ones. | |||||
*/ | |||||
void updateMatchedPairs() noexcept; | |||||
/** Forces a sort of the sequence. | |||||
You may need to call this if you've manually modified the timestamps of some | |||||
events such that the overall order now needs updating. | |||||
*/ | |||||
void sort() noexcept; | |||||
//============================================================================== | |||||
/** Copies all the messages for a particular midi channel to another sequence. | |||||
@param channelNumberToExtract the midi channel to look for, in the range 1 to 16 | |||||
@param destSequence the sequence that the chosen events should be copied to | |||||
@param alsoIncludeMetaEvents if true, any meta-events (which don't apply to a specific | |||||
channel) will also be copied across. | |||||
@see extractSysExMessages | |||||
*/ | |||||
void extractMidiChannelMessages (int channelNumberToExtract, | |||||
MidiMessageSequence& destSequence, | |||||
bool alsoIncludeMetaEvents) const; | |||||
/** Copies all midi sys-ex messages to another sequence. | |||||
@param destSequence this is the sequence to which any sys-exes in this sequence | |||||
will be added | |||||
@see extractMidiChannelMessages | |||||
*/ | |||||
void extractSysExMessages (MidiMessageSequence& destSequence) const; | |||||
/** Removes any messages in this sequence that have a specific midi channel. | |||||
@param channelNumberToRemove the midi channel to look for, in the range 1 to 16 | |||||
*/ | |||||
void deleteMidiChannelMessages (int channelNumberToRemove); | |||||
/** Removes any sys-ex messages from this sequence. */ | |||||
void deleteSysExMessages(); | |||||
/** Adds an offset to the timestamps of all events in the sequence. | |||||
@param deltaTime the amount to add to each timestamp. | |||||
*/ | |||||
void addTimeToMessages (double deltaTime) noexcept; | |||||
//============================================================================== | |||||
/** Scans through the sequence to determine the state of any midi controllers at | |||||
a given time. | |||||
This will create a sequence of midi controller changes that can be | |||||
used to set all midi controllers to the state they would be in at the | |||||
specified time within this sequence. | |||||
As well as controllers, it will also recreate the midi program number | |||||
and pitch bend position. | |||||
@param channelNumber the midi channel to look for, in the range 1 to 16. Controllers | |||||
for other channels will be ignored. | |||||
@param time the time at which you want to find out the state - there are | |||||
no explicit units for this time measurement, it's the same units | |||||
as used for the timestamps of the messages | |||||
@param resultMessages an array to which midi controller-change messages will be added. This | |||||
will be the minimum number of controller changes to recreate the | |||||
state at the required time. | |||||
*/ | |||||
void createControllerUpdatesForTime (int channelNumber, double time, | |||||
Array<MidiMessage>& resultMessages); | |||||
//============================================================================== | |||||
/** Swaps this sequence with another one. */ | |||||
void swapWith (MidiMessageSequence&) noexcept; | |||||
private: | |||||
//============================================================================== | |||||
friend class MidiFile; | |||||
OwnedArray<MidiEventHolder> list; | |||||
MidiEventHolder* addEvent (MidiEventHolder*, double); | |||||
JUCE_LEAK_DETECTOR (MidiMessageSequence) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,376 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
MidiRPNDetector::MidiRPNDetector() noexcept | |||||
{ | |||||
} | |||||
MidiRPNDetector::~MidiRPNDetector() noexcept | |||||
{ | |||||
} | |||||
bool MidiRPNDetector::parseControllerMessage (int midiChannel, | |||||
int controllerNumber, | |||||
int controllerValue, | |||||
MidiRPNMessage& result) noexcept | |||||
{ | |||||
jassert (midiChannel >= 1 && midiChannel <= 16); | |||||
jassert (controllerNumber >= 0 && controllerNumber < 128); | |||||
jassert (controllerValue >= 0 && controllerValue < 128); | |||||
return states[midiChannel - 1].handleController (midiChannel, controllerNumber, controllerValue, result); | |||||
} | |||||
void MidiRPNDetector::reset() noexcept | |||||
{ | |||||
for (int i = 0; i < 16; ++i) | |||||
{ | |||||
states[i].parameterMSB = 0xff; | |||||
states[i].parameterLSB = 0xff; | |||||
states[i].resetValue(); | |||||
states[i].isNRPN = false; | |||||
} | |||||
} | |||||
//============================================================================== | |||||
MidiRPNDetector::ChannelState::ChannelState() noexcept | |||||
: parameterMSB (0xff), parameterLSB (0xff), valueMSB (0xff), valueLSB (0xff), isNRPN (false) | |||||
{ | |||||
} | |||||
bool MidiRPNDetector::ChannelState::handleController (int channel, | |||||
int controllerNumber, | |||||
int value, | |||||
MidiRPNMessage& result) noexcept | |||||
{ | |||||
switch (controllerNumber) | |||||
{ | |||||
case 0x62: parameterLSB = uint8 (value); resetValue(); isNRPN = true; break; | |||||
case 0x63: parameterMSB = uint8 (value); resetValue(); isNRPN = true; break; | |||||
case 0x64: parameterLSB = uint8 (value); resetValue(); isNRPN = false; break; | |||||
case 0x65: parameterMSB = uint8 (value); resetValue(); isNRPN = false; break; | |||||
case 0x06: valueMSB = uint8 (value); return sendIfReady (channel, result); | |||||
case 0x26: valueLSB = uint8 (value); break; | |||||
default: break; | |||||
} | |||||
return false; | |||||
} | |||||
void MidiRPNDetector::ChannelState::resetValue() noexcept | |||||
{ | |||||
valueMSB = 0xff; | |||||
valueLSB = 0xff; | |||||
} | |||||
//============================================================================== | |||||
bool MidiRPNDetector::ChannelState::sendIfReady (int channel, MidiRPNMessage& result) noexcept | |||||
{ | |||||
if (parameterMSB < 0x80 && parameterLSB < 0x80) | |||||
{ | |||||
if (valueMSB < 0x80) | |||||
{ | |||||
result.channel = channel; | |||||
result.parameterNumber = (parameterMSB << 7) + parameterLSB; | |||||
result.isNRPN = isNRPN; | |||||
if (valueLSB < 0x80) | |||||
{ | |||||
result.value = (valueMSB << 7) + valueLSB; | |||||
result.is14BitValue = true; | |||||
} | |||||
else | |||||
{ | |||||
result.value = valueMSB; | |||||
result.is14BitValue = false; | |||||
} | |||||
return true; | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
//============================================================================== | |||||
MidiBuffer MidiRPNGenerator::generate (MidiRPNMessage message) | |||||
{ | |||||
return generate (message.channel, | |||||
message.parameterNumber, | |||||
message.value, | |||||
message.isNRPN, | |||||
message.is14BitValue); | |||||
} | |||||
MidiBuffer MidiRPNGenerator::generate (int midiChannel, | |||||
int parameterNumber, | |||||
int value, | |||||
bool isNRPN, | |||||
bool use14BitValue) | |||||
{ | |||||
jassert (midiChannel > 0 && midiChannel <= 16); | |||||
jassert (parameterNumber >= 0 && parameterNumber < 16384); | |||||
jassert (value >= 0 && value < (use14BitValue ? 16384 : 128)); | |||||
uint8 parameterLSB = uint8 (parameterNumber & 0x0000007f); | |||||
uint8 parameterMSB = uint8 (parameterNumber >> 7); | |||||
uint8 valueLSB = use14BitValue ? uint8 (value & 0x0000007f) : 0x00; | |||||
uint8 valueMSB = use14BitValue ? uint8 (value >> 7) : uint8 (value); | |||||
uint8 channelByte = uint8 (0xb0 + midiChannel - 1); | |||||
MidiBuffer buffer; | |||||
buffer.addEvent (MidiMessage (channelByte, isNRPN ? 0x62 : 0x64, parameterLSB), 0); | |||||
buffer.addEvent (MidiMessage (channelByte, isNRPN ? 0x63 : 0x65, parameterMSB), 0); | |||||
// sending the value LSB is optional, but must come before sending the value MSB: | |||||
if (use14BitValue) | |||||
buffer.addEvent (MidiMessage (channelByte, 0x26, valueLSB), 0); | |||||
buffer.addEvent (MidiMessage (channelByte, 0x06, valueMSB), 0); | |||||
return buffer; | |||||
} | |||||
//============================================================================== | |||||
//============================================================================== | |||||
#if JUCE_UNIT_TESTS | |||||
class MidiRPNDetectorTests : public UnitTest | |||||
{ | |||||
public: | |||||
MidiRPNDetectorTests() : UnitTest ("MidiRPNDetector class", "MIDI/MPE") {} | |||||
void runTest() override | |||||
{ | |||||
beginTest ("7-bit RPN"); | |||||
{ | |||||
MidiRPNDetector detector; | |||||
MidiRPNMessage rpn; | |||||
expect (! detector.parseControllerMessage (2, 101, 0, rpn)); | |||||
expect (! detector.parseControllerMessage (2, 100, 7, rpn)); | |||||
expect (detector.parseControllerMessage (2, 6, 42, rpn)); | |||||
expectEquals (rpn.channel, 2); | |||||
expectEquals (rpn.parameterNumber, 7); | |||||
expectEquals (rpn.value, 42); | |||||
expect (! rpn.isNRPN); | |||||
expect (! rpn.is14BitValue); | |||||
} | |||||
beginTest ("14-bit RPN"); | |||||
{ | |||||
MidiRPNDetector detector; | |||||
MidiRPNMessage rpn; | |||||
expect (! detector.parseControllerMessage (1, 100, 44, rpn)); | |||||
expect (! detector.parseControllerMessage (1, 101, 2, rpn)); | |||||
expect (! detector.parseControllerMessage (1, 38, 94, rpn)); | |||||
expect (detector.parseControllerMessage (1, 6, 1, rpn)); | |||||
expectEquals (rpn.channel, 1); | |||||
expectEquals (rpn.parameterNumber, 300); | |||||
expectEquals (rpn.value, 222); | |||||
expect (! rpn.isNRPN); | |||||
expect (rpn.is14BitValue); | |||||
} | |||||
beginTest ("RPNs on multiple channels simultaneously"); | |||||
{ | |||||
MidiRPNDetector detector; | |||||
MidiRPNMessage rpn; | |||||
expect (! detector.parseControllerMessage (1, 100, 44, rpn)); | |||||
expect (! detector.parseControllerMessage (2, 101, 0, rpn)); | |||||
expect (! detector.parseControllerMessage (1, 101, 2, rpn)); | |||||
expect (! detector.parseControllerMessage (2, 100, 7, rpn)); | |||||
expect (! detector.parseControllerMessage (1, 38, 94, rpn)); | |||||
expect (detector.parseControllerMessage (2, 6, 42, rpn)); | |||||
expectEquals (rpn.channel, 2); | |||||
expectEquals (rpn.parameterNumber, 7); | |||||
expectEquals (rpn.value, 42); | |||||
expect (! rpn.isNRPN); | |||||
expect (! rpn.is14BitValue); | |||||
expect (detector.parseControllerMessage (1, 6, 1, rpn)); | |||||
expectEquals (rpn.channel, 1); | |||||
expectEquals (rpn.parameterNumber, 300); | |||||
expectEquals (rpn.value, 222); | |||||
expect (! rpn.isNRPN); | |||||
expect (rpn.is14BitValue); | |||||
} | |||||
beginTest ("14-bit RPN with value within 7-bit range"); | |||||
{ | |||||
MidiRPNDetector detector; | |||||
MidiRPNMessage rpn; | |||||
expect (! detector.parseControllerMessage (16, 100, 0 , rpn)); | |||||
expect (! detector.parseControllerMessage (16, 101, 0, rpn)); | |||||
expect (! detector.parseControllerMessage (16, 38, 3, rpn)); | |||||
expect (detector.parseControllerMessage (16, 6, 0, rpn)); | |||||
expectEquals (rpn.channel, 16); | |||||
expectEquals (rpn.parameterNumber, 0); | |||||
expectEquals (rpn.value, 3); | |||||
expect (! rpn.isNRPN); | |||||
expect (rpn.is14BitValue); | |||||
} | |||||
beginTest ("invalid RPN (wrong order)"); | |||||
{ | |||||
MidiRPNDetector detector; | |||||
MidiRPNMessage rpn; | |||||
expect (! detector.parseControllerMessage (2, 6, 42, rpn)); | |||||
expect (! detector.parseControllerMessage (2, 101, 0, rpn)); | |||||
expect (! detector.parseControllerMessage (2, 100, 7, rpn)); | |||||
} | |||||
beginTest ("14-bit RPN interspersed with unrelated CC messages"); | |||||
{ | |||||
MidiRPNDetector detector; | |||||
MidiRPNMessage rpn; | |||||
expect (! detector.parseControllerMessage (16, 3, 80, rpn)); | |||||
expect (! detector.parseControllerMessage (16, 100, 0 , rpn)); | |||||
expect (! detector.parseControllerMessage (16, 4, 81, rpn)); | |||||
expect (! detector.parseControllerMessage (16, 101, 0, rpn)); | |||||
expect (! detector.parseControllerMessage (16, 5, 82, rpn)); | |||||
expect (! detector.parseControllerMessage (16, 5, 83, rpn)); | |||||
expect (! detector.parseControllerMessage (16, 38, 3, rpn)); | |||||
expect (! detector.parseControllerMessage (16, 4, 84, rpn)); | |||||
expect (! detector.parseControllerMessage (16, 3, 85, rpn)); | |||||
expect (detector.parseControllerMessage (16, 6, 0, rpn)); | |||||
expectEquals (rpn.channel, 16); | |||||
expectEquals (rpn.parameterNumber, 0); | |||||
expectEquals (rpn.value, 3); | |||||
expect (! rpn.isNRPN); | |||||
expect (rpn.is14BitValue); | |||||
} | |||||
beginTest ("14-bit NRPN"); | |||||
{ | |||||
MidiRPNDetector detector; | |||||
MidiRPNMessage rpn; | |||||
expect (! detector.parseControllerMessage (1, 98, 44, rpn)); | |||||
expect (! detector.parseControllerMessage (1, 99 , 2, rpn)); | |||||
expect (! detector.parseControllerMessage (1, 38, 94, rpn)); | |||||
expect (detector.parseControllerMessage (1, 6, 1, rpn)); | |||||
expectEquals (rpn.channel, 1); | |||||
expectEquals (rpn.parameterNumber, 300); | |||||
expectEquals (rpn.value, 222); | |||||
expect (rpn.isNRPN); | |||||
expect (rpn.is14BitValue); | |||||
} | |||||
beginTest ("reset"); | |||||
{ | |||||
MidiRPNDetector detector; | |||||
MidiRPNMessage rpn; | |||||
expect (! detector.parseControllerMessage (2, 101, 0, rpn)); | |||||
detector.reset(); | |||||
expect (! detector.parseControllerMessage (2, 100, 7, rpn)); | |||||
expect (! detector.parseControllerMessage (2, 6, 42, rpn)); | |||||
} | |||||
} | |||||
}; | |||||
static MidiRPNDetectorTests MidiRPNDetectorUnitTests; | |||||
//============================================================================== | |||||
class MidiRPNGeneratorTests : public UnitTest | |||||
{ | |||||
public: | |||||
MidiRPNGeneratorTests() : UnitTest ("MidiRPNGenerator class", "MIDI/MPE") {} | |||||
void runTest() override | |||||
{ | |||||
beginTest ("generating RPN/NRPN"); | |||||
{ | |||||
{ | |||||
MidiBuffer buffer = MidiRPNGenerator::generate (1, 23, 1337, true, true); | |||||
expectContainsRPN (buffer, 1, 23, 1337, true, true); | |||||
} | |||||
{ | |||||
MidiBuffer buffer = MidiRPNGenerator::generate (16, 101, 34, false, false); | |||||
expectContainsRPN (buffer, 16, 101, 34, false, false); | |||||
} | |||||
{ | |||||
MidiRPNMessage message = { 16, 101, 34, false, false }; | |||||
MidiBuffer buffer = MidiRPNGenerator::generate (message); | |||||
expectContainsRPN (buffer, message); | |||||
} | |||||
} | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
void expectContainsRPN (const MidiBuffer& midiBuffer, | |||||
int channel, | |||||
int parameterNumber, | |||||
int value, | |||||
bool isNRPN, | |||||
bool is14BitValue) | |||||
{ | |||||
MidiRPNMessage expected = { channel, parameterNumber, value, isNRPN, is14BitValue }; | |||||
expectContainsRPN (midiBuffer, expected); | |||||
} | |||||
//============================================================================== | |||||
void expectContainsRPN (const MidiBuffer& midiBuffer, MidiRPNMessage expected) | |||||
{ | |||||
MidiBuffer::Iterator iter (midiBuffer); | |||||
MidiMessage midiMessage; | |||||
MidiRPNMessage result = MidiRPNMessage(); | |||||
MidiRPNDetector detector; | |||||
int samplePosition; // not actually used, so no need to initialise. | |||||
while (iter.getNextEvent (midiMessage, samplePosition)) | |||||
{ | |||||
if (detector.parseControllerMessage (midiMessage.getChannel(), | |||||
midiMessage.getControllerNumber(), | |||||
midiMessage.getControllerValue(), | |||||
result)) | |||||
break; | |||||
} | |||||
expectEquals (result.channel, expected.channel); | |||||
expectEquals (result.parameterNumber, expected.parameterNumber); | |||||
expectEquals (result.value, expected.value); | |||||
expect (result.isNRPN == expected.isNRPN); | |||||
expect (result.is14BitValue == expected.is14BitValue); | |||||
} | |||||
}; | |||||
static MidiRPNGeneratorTests MidiRPNGeneratorUnitTests; | |||||
#endif // JUCE_UNIT_TESTS | |||||
} // namespace juce |
@@ -0,0 +1,148 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** Represents a MIDI RPN (registered parameter number) or NRPN (non-registered | |||||
parameter number) message. | |||||
*/ | |||||
struct MidiRPNMessage | |||||
{ | |||||
/** Midi channel of the message, in the range 1 to 16. */ | |||||
int channel; | |||||
/** The 14-bit parameter index, in the range 0 to 16383 (0x3fff). */ | |||||
int parameterNumber; | |||||
/** The parameter value, in the range 0 to 16383 (0x3fff). | |||||
If the message contains no value LSB, the value will be in the range | |||||
0 to 127 (0x7f). | |||||
*/ | |||||
int value; | |||||
/** True if this message is an NRPN; false if it is an RPN. */ | |||||
bool isNRPN; | |||||
/** True if the value uses 14-bit resolution (LSB + MSB); false if | |||||
the value is 7-bit (MSB only). | |||||
*/ | |||||
bool is14BitValue; | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
Parses a stream of MIDI data to assemble RPN and NRPN messages from their | |||||
constituent MIDI CC messages. | |||||
The detector uses the following parsing rules: the parameter number | |||||
LSB/MSB can be sent/received in either order and must both come before the | |||||
parameter value; for the parameter value, LSB always has to be sent/received | |||||
before the value MSB, otherwise it will be treated as 7-bit (MSB only). | |||||
*/ | |||||
class JUCE_API MidiRPNDetector | |||||
{ | |||||
public: | |||||
/** Constructor. */ | |||||
MidiRPNDetector() noexcept; | |||||
/** Destructor. */ | |||||
~MidiRPNDetector() noexcept; | |||||
/** Resets the RPN detector's internal state, so that it forgets about | |||||
previously received MIDI CC messages. | |||||
*/ | |||||
void reset() noexcept; | |||||
//============================================================================== | |||||
/** Takes the next in a stream of incoming MIDI CC messages and returns true | |||||
if it forms the last of a sequence that makes an RPN or NPRN. | |||||
If this returns true, then the RPNMessage object supplied will be | |||||
filled-out with the message's details. | |||||
(If it returns false then the RPNMessage object will be unchanged). | |||||
*/ | |||||
bool parseControllerMessage (int midiChannel, | |||||
int controllerNumber, | |||||
int controllerValue, | |||||
MidiRPNMessage& result) noexcept; | |||||
private: | |||||
//============================================================================== | |||||
struct ChannelState | |||||
{ | |||||
ChannelState() noexcept; | |||||
bool handleController (int channel, int controllerNumber, | |||||
int value, MidiRPNMessage&) noexcept; | |||||
void resetValue() noexcept; | |||||
bool sendIfReady (int channel, MidiRPNMessage&) noexcept; | |||||
uint8 parameterMSB, parameterLSB, valueMSB, valueLSB; | |||||
bool isNRPN; | |||||
}; | |||||
//============================================================================== | |||||
ChannelState states[16]; | |||||
JUCE_LEAK_DETECTOR (MidiRPNDetector) | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
Generates an appropriate sequence of MIDI CC messages to represent an RPN | |||||
or NRPN message. | |||||
This sequence (as a MidiBuffer) can then be directly sent to a MidiOutput. | |||||
*/ | |||||
class JUCE_API MidiRPNGenerator | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Generates a MIDI sequence representing the given RPN or NRPN message. */ | |||||
static MidiBuffer generate (MidiRPNMessage message); | |||||
//============================================================================== | |||||
/** Generates a MIDI sequence representing an RPN or NRPN message with the | |||||
given parameters. | |||||
@param channel The MIDI channel of the RPN/NRPN message. | |||||
@param parameterNumber The parameter number, in the range 0 to 16383. | |||||
@param value The parameter value, in the range 0 to 16383, or | |||||
in the range 0 to 127 if sendAs14BitValue is false. | |||||
@param isNRPN Whether you need a MIDI RPN or NRPN sequence (RPN is default). | |||||
@param use14BitValue If true (default), the value will have 14-bit precision | |||||
(two MIDI bytes). If false, instead the value will have | |||||
7-bit presision (a single MIDI byte). | |||||
*/ | |||||
static MidiBuffer generate (int channel, | |||||
int parameterNumber, | |||||
int value, | |||||
bool isNRPN = false, | |||||
bool use14BitValue = true); | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,378 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/* | |||||
This class represents an instrument handling MPE. | |||||
It has an MPE zone layout and maintans a state of currently | |||||
active (playing) notes and the values of their dimensions of expression. | |||||
You can trigger and modulate notes: | |||||
- by passing MIDI messages with the method processNextMidiEvent; | |||||
- by directly calling the methods noteOn, noteOff etc. | |||||
The class implements the channel and note management logic specified in | |||||
MPE. If you pass it a message, it will know what notes on what | |||||
channels (if any) should be affected by that message. | |||||
The class has a Listener class with the three callbacks MPENoteAdded, | |||||
MPENoteChanged, and MPENoteFinished. Implement such a | |||||
Listener class to react to note changes and trigger some functionality for | |||||
your application that depends on the MPE note state. | |||||
For example, you can use this class to write an MPE visualiser. | |||||
If you want to write a real-time audio synth with MPE functionality, | |||||
you should instead use the classes MPESynthesiserBase, which adds | |||||
the ability to render audio and to manage voices. | |||||
@see MPENote, MPEZoneLayout, MPESynthesiser | |||||
*/ | |||||
class JUCE_API MPEInstrument | |||||
{ | |||||
public: | |||||
/** Constructor. | |||||
This will construct an MPE instrument with initially no MPE zones. | |||||
In order to process incoming MIDI, call setZoneLayout, define the layout | |||||
via MIDI RPN messages, or set the instrument to legacy mode. | |||||
*/ | |||||
MPEInstrument() noexcept; | |||||
/** Destructor. */ | |||||
virtual ~MPEInstrument(); | |||||
//============================================================================== | |||||
/** Returns the current zone layout of the instrument. | |||||
This happens by value, to enforce thread-safety and class invariants. | |||||
Note: If the instrument is in legacy mode, the return value of this | |||||
method is unspecified. | |||||
*/ | |||||
MPEZoneLayout getZoneLayout() const noexcept; | |||||
/** Re-sets the zone layout of the instrument to the one passed in. | |||||
As a side effect, this will discard all currently playing notes, | |||||
and call noteReleased for all of them. | |||||
This will also disable legacy mode in case it was enabled previously. | |||||
*/ | |||||
void setZoneLayout (MPEZoneLayout newLayout); | |||||
/** Returns true if the given MIDI channel (1-16) is a note channel in any | |||||
of the MPEInstrument's MPE zones; false otherwise. | |||||
When in legacy mode, this will return true if the given channel is | |||||
contained in the current legacy mode channel range; false otherwise. | |||||
*/ | |||||
bool isNoteChannel (int midiChannel) const noexcept; | |||||
/** Returns true if the given MIDI channel (1-16) is a master channel in any | |||||
of the MPEInstrument's MPE zones; false otherwise. | |||||
When in legacy mode, this will always return false. | |||||
*/ | |||||
bool isMasterChannel (int midiChannel) const noexcept; | |||||
//============================================================================== | |||||
/** The MPE note tracking mode. In case there is more than one note playing | |||||
simultaneously on the same MIDI channel, this determines which of these | |||||
notes will be modulated by an incoming MPE message on that channel | |||||
(pressure, pitchbend, or timbre). | |||||
The default is lastNotePlayedOnChannel. | |||||
*/ | |||||
enum TrackingMode | |||||
{ | |||||
lastNotePlayedOnChannel, //! The most recent note on the channel that is still played (key down and/or sustained) | |||||
lowestNoteOnChannel, //! The lowest note (by initialNote) on the channel with the note key still down | |||||
highestNoteOnChannel, //! The highest note (by initialNote) on the channel with the note key still down | |||||
allNotesOnChannel //! All notes on the channel (key down and/or sustained) | |||||
}; | |||||
/** Set the MPE tracking mode for the pressure dimension. */ | |||||
void setPressureTrackingMode (TrackingMode modeToUse); | |||||
/** Set the MPE tracking mode for the pitchbend dimension. */ | |||||
void setPitchbendTrackingMode (TrackingMode modeToUse); | |||||
/** Set the MPE tracking mode for the timbre dimension. */ | |||||
void setTimbreTrackingMode (TrackingMode modeToUse); | |||||
//============================================================================== | |||||
/** Process a MIDI message and trigger the appropriate method calls | |||||
(noteOn, noteOff etc.) | |||||
You can override this method if you need some special MIDI message | |||||
treatment on top of the standard MPE logic implemented here. | |||||
*/ | |||||
virtual void processNextMidiEvent (const MidiMessage& message); | |||||
//============================================================================== | |||||
/** Request a note-on on the given channel, with the given initial note | |||||
number and velocity. | |||||
If the message arrives on a valid note channel, this will create a | |||||
new MPENote and call the noteAdded callback. | |||||
*/ | |||||
virtual void noteOn (int midiChannel, int midiNoteNumber, MPEValue midiNoteOnVelocity); | |||||
/** Request a note-off. If there is a matching playing note, this will | |||||
release the note (except if it is sustained by a sustain or sostenuto | |||||
pedal) and call the noteReleased callback. | |||||
*/ | |||||
virtual void noteOff (int midiChannel, int midiNoteNumber, MPEValue midiNoteOffVelocity); | |||||
/** Request a pitchbend on the given channel with the given value (in units | |||||
of MIDI pitchwheel position). | |||||
Internally, this will determine whether the pitchwheel move is a | |||||
per-note pitchbend or a master pitchbend (depending on midiChannel), | |||||
take the correct per-note or master pitchbend range of the affected MPE | |||||
zone, and apply the resulting pitchbend to the affected note(s) (if any). | |||||
*/ | |||||
virtual void pitchbend (int midiChannel, MPEValue pitchbend); | |||||
/** Request a pressure change on the given channel with the given value. | |||||
This will modify the pressure dimension of the note currently held down | |||||
on this channel (if any). If the channel is a zone master channel, | |||||
the pressure change will be broadcast to all notes in this zone. | |||||
*/ | |||||
virtual void pressure (int midiChannel, MPEValue value); | |||||
/** Request a third dimension (timbre) change on the given channel with the | |||||
given value. | |||||
This will modify the timbre dimension of the note currently held down | |||||
on this channel (if any). If the channel is a zone master channel, | |||||
the timbre change will be broadcast to all notes in this zone. | |||||
*/ | |||||
virtual void timbre (int midiChannel, MPEValue value); | |||||
/** Request a sustain pedal press or release. If midiChannel is a zone's | |||||
master channel, this will act on all notes in that zone; otherwise, | |||||
nothing will happen. | |||||
*/ | |||||
virtual void sustainPedal (int midiChannel, bool isDown); | |||||
/** Request a sostenuto pedal press or release. If midiChannel is a zone's | |||||
master channel, this will act on all notes in that zone; otherwise, | |||||
nothing will happen. | |||||
*/ | |||||
virtual void sostenutoPedal (int midiChannel, bool isDown); | |||||
/** Discard all currently playing notes. | |||||
This will also call the noteReleased listener callback for all of them. | |||||
*/ | |||||
void releaseAllNotes(); | |||||
//============================================================================== | |||||
/** Returns the number of MPE notes currently played by the | |||||
instrument. | |||||
*/ | |||||
int getNumPlayingNotes() const noexcept; | |||||
/** Returns the note at the given index. If there is no such note, returns | |||||
an invalid MPENote. The notes are sorted such that the most recently | |||||
added note is the last element. | |||||
*/ | |||||
MPENote getNote (int index) const noexcept; | |||||
/** Returns the note currently playing on the given midiChannel with the | |||||
specified initial MIDI note number, if there is such a note. | |||||
Otherwise, this returns an invalid MPENote | |||||
(check with note.isValid() before use!) | |||||
*/ | |||||
MPENote getNote (int midiChannel, int midiNoteNumber) const noexcept; | |||||
/** Returns the most recent note that is playing on the given midiChannel | |||||
(this will be the note which has received the most recent note-on without | |||||
a corresponding note-off), if there is such a note. | |||||
Otherwise, this returns an invalid MPENote | |||||
(check with note.isValid() before use!) | |||||
*/ | |||||
MPENote getMostRecentNote (int midiChannel) const noexcept; | |||||
/** Returns the most recent note that is not the note passed in. | |||||
If there is no such note, this returns an invalid MPENote | |||||
(check with note.isValid() before use!) | |||||
This helper method might be useful for some custom voice handling algorithms. | |||||
*/ | |||||
MPENote getMostRecentNoteOtherThan (MPENote otherThanThisNote) const noexcept; | |||||
//============================================================================== | |||||
/** Derive from this class to be informed about any changes in the expressive | |||||
MIDI notes played by this instrument. | |||||
Note: This listener type receives its callbacks immediately, and not | |||||
via the message thread (so you might be for example in the MIDI thread). | |||||
Therefore you should never do heavy work such as graphics rendering etc. | |||||
inside those callbacks. | |||||
*/ | |||||
class JUCE_API Listener | |||||
{ | |||||
public: | |||||
/** Destructor. */ | |||||
virtual ~Listener() {} | |||||
/** Implement this callback to be informed whenever a new expressive | |||||
MIDI note is triggered. | |||||
*/ | |||||
virtual void noteAdded (MPENote newNote) = 0; | |||||
/** Implement this callback to be informed whenever a currently | |||||
playing MPE note's pressure value changes. | |||||
*/ | |||||
virtual void notePressureChanged (MPENote changedNote) = 0; | |||||
/** Implement this callback to be informed whenever a currently | |||||
playing MPE note's pitchbend value changes. | |||||
Note: This can happen if the note itself is bent, if there is a | |||||
master channel pitchbend event, or if both occur simultaneously. | |||||
Call MPENote::getFrequencyInHertz to get the effective note frequency. | |||||
*/ | |||||
virtual void notePitchbendChanged (MPENote changedNote) = 0; | |||||
/** Implement this callback to be informed whenever a currently | |||||
playing MPE note's timbre value changes. | |||||
*/ | |||||
virtual void noteTimbreChanged (MPENote changedNote) = 0; | |||||
/** Implement this callback to be informed whether a currently playing | |||||
MPE note's key state (whether the key is down and/or the note is | |||||
sustained) has changed. | |||||
Note: if the key state changes to MPENote::off, noteReleased is | |||||
called instead. | |||||
*/ | |||||
virtual void noteKeyStateChanged (MPENote changedNote) = 0; | |||||
/** Implement this callback to be informed whenever an MPE note | |||||
is released (either by a note-off message, or by a sustain/sostenuto | |||||
pedal release for a note that already received a note-off), | |||||
and should therefore stop playing. | |||||
*/ | |||||
virtual void noteReleased (MPENote finishedNote) = 0; | |||||
}; | |||||
//============================================================================== | |||||
/** Adds a listener. */ | |||||
void addListener (Listener* listenerToAdd) noexcept; | |||||
/** Removes a listener. */ | |||||
void removeListener (Listener* listenerToRemove) noexcept; | |||||
//============================================================================== | |||||
/** Puts the instrument into legacy mode. | |||||
As a side effect, this will discard all currently playing notes, | |||||
and call noteReleased for all of them. | |||||
This special zone layout mode is for backwards compatibility with | |||||
non-MPE MIDI devices. In this mode, the instrument will ignore the | |||||
current MPE zone layout. It will instead take a range of MIDI channels | |||||
(default: all channels 1-16) and treat them as note channels, with no | |||||
master channel. MIDI channels outside of this range will be ignored. | |||||
@param pitchbendRange The note pitchbend range in semitones to use when in legacy mode. | |||||
Must be between 0 and 96, otherwise behaviour is undefined. | |||||
The default pitchbend range in legacy mode is +/- 2 semitones. | |||||
@param channelRange The range of MIDI channels to use for notes when in legacy mode. | |||||
The default is to use all MIDI channels (1-16). | |||||
To get out of legacy mode, set a new MPE zone layout using setZoneLayout. | |||||
*/ | |||||
void enableLegacyMode (int pitchbendRange = 2, | |||||
Range<int> channelRange = Range<int> (1, 17)); | |||||
/** Returns true if the instrument is in legacy mode, false otherwise. */ | |||||
bool isLegacyModeEnabled() const noexcept; | |||||
/** Returns the range of MIDI channels (1-16) to be used for notes when in legacy mode. */ | |||||
Range<int> getLegacyModeChannelRange() const noexcept; | |||||
/** Re-sets the range of MIDI channels (1-16) to be used for notes when in legacy mode. */ | |||||
void setLegacyModeChannelRange (Range<int> channelRange); | |||||
/** Returns the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ | |||||
int getLegacyModePitchbendRange() const noexcept; | |||||
/** Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ | |||||
void setLegacyModePitchbendRange (int pitchbendRange); | |||||
private: | |||||
//============================================================================== | |||||
CriticalSection lock; | |||||
Array<MPENote> notes; | |||||
MPEZoneLayout zoneLayout; | |||||
ListenerList<Listener> listeners; | |||||
uint8 lastPressureLowerBitReceivedOnChannel[16]; | |||||
uint8 lastTimbreLowerBitReceivedOnChannel[16]; | |||||
bool isNoteChannelSustained[16]; | |||||
struct LegacyMode | |||||
{ | |||||
bool isEnabled; | |||||
Range<int> channelRange; | |||||
int pitchbendRange; | |||||
}; | |||||
struct MPEDimension | |||||
{ | |||||
MPEDimension() noexcept : trackingMode (lastNotePlayedOnChannel) {} | |||||
TrackingMode trackingMode; | |||||
MPEValue lastValueReceivedOnChannel[16]; | |||||
MPEValue MPENote::* value; | |||||
MPEValue& getValue (MPENote& note) noexcept { return note.*(value); } | |||||
}; | |||||
LegacyMode legacyMode; | |||||
MPEDimension pitchbendDimension, pressureDimension, timbreDimension; | |||||
void updateDimension (int midiChannel, MPEDimension&, MPEValue); | |||||
void updateDimensionMaster (MPEZone&, MPEDimension&, MPEValue); | |||||
void updateDimensionForNote (MPENote&, MPEDimension&, MPEValue); | |||||
void callListenersDimensionChanged (MPENote&, MPEDimension&); | |||||
MPEValue getInitialValueForNewNote (int midiChannel, MPEDimension&) const; | |||||
void processMidiNoteOnMessage (const MidiMessage&); | |||||
void processMidiNoteOffMessage (const MidiMessage&); | |||||
void processMidiPitchWheelMessage (const MidiMessage&); | |||||
void processMidiChannelPressureMessage (const MidiMessage&); | |||||
void processMidiControllerMessage (const MidiMessage&); | |||||
void processMidiAllNotesOffMessage (const MidiMessage&); | |||||
void handlePressureMSB (int midiChannel, int value) noexcept; | |||||
void handlePressureLSB (int midiChannel, int value) noexcept; | |||||
void handleTimbreMSB (int midiChannel, int value) noexcept; | |||||
void handleTimbreLSB (int midiChannel, int value) noexcept; | |||||
void handleSustainOrSostenuto (int midiChannel, bool isDown, bool isSostenuto); | |||||
MPENote* getNotePtr (int midiChannel, int midiNoteNumber) const noexcept; | |||||
MPENote* getNotePtr (int midiChannel, TrackingMode) const noexcept; | |||||
MPENote* getLastNotePlayedPtr (int midiChannel) const noexcept; | |||||
MPENote* getHighestNotePtr (int midiChannel) const noexcept; | |||||
MPENote* getLowestNotePtr (int midiChannel) const noexcept; | |||||
void updateNoteTotalPitchbend (MPENote&); | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPEInstrument) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,200 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
MidiBuffer MPEMessages::addZone (MPEZone zone) | |||||
{ | |||||
MidiBuffer buffer (MidiRPNGenerator::generate (zone.getFirstNoteChannel(), | |||||
zoneLayoutMessagesRpnNumber, | |||||
zone.getNumNoteChannels(), | |||||
false, false)); | |||||
buffer.addEvents (perNotePitchbendRange (zone), 0, -1, 0); | |||||
buffer.addEvents (masterPitchbendRange (zone), 0, -1, 0); | |||||
return buffer; | |||||
} | |||||
MidiBuffer MPEMessages::perNotePitchbendRange (MPEZone zone) | |||||
{ | |||||
return MidiRPNGenerator::generate (zone.getFirstNoteChannel(), 0, | |||||
zone.getPerNotePitchbendRange(), | |||||
false, false); | |||||
} | |||||
MidiBuffer MPEMessages::masterPitchbendRange (MPEZone zone) | |||||
{ | |||||
return MidiRPNGenerator::generate (zone.getMasterChannel(), 0, | |||||
zone.getMasterPitchbendRange(), | |||||
false, false); | |||||
} | |||||
MidiBuffer MPEMessages::clearAllZones() | |||||
{ | |||||
return MidiRPNGenerator::generate (1, zoneLayoutMessagesRpnNumber, 16, false, false); | |||||
} | |||||
MidiBuffer MPEMessages::setZoneLayout (const MPEZoneLayout& layout) | |||||
{ | |||||
MidiBuffer buffer; | |||||
buffer.addEvents (clearAllZones(), 0, -1, 0); | |||||
for (int i = 0; i < layout.getNumZones(); ++i) | |||||
buffer.addEvents (addZone (*layout.getZoneByIndex (i)), 0, -1, 0); | |||||
return buffer; | |||||
} | |||||
//============================================================================== | |||||
//============================================================================== | |||||
#if JUCE_UNIT_TESTS | |||||
class MPEMessagesTests : public UnitTest | |||||
{ | |||||
public: | |||||
MPEMessagesTests() : UnitTest ("MPEMessages class", "MIDI/MPE") {} | |||||
void runTest() override | |||||
{ | |||||
beginTest ("add zone"); | |||||
{ | |||||
{ | |||||
MidiBuffer buffer = MPEMessages::addZone (MPEZone (1, 7)); | |||||
const uint8 expectedBytes[] = | |||||
{ | |||||
0xb1, 0x64, 0x06, 0xb1, 0x65, 0x00, 0xb1, 0x06, 0x07, // set up zone | |||||
0xb1, 0x64, 0x00, 0xb1, 0x65, 0x00, 0xb1, 0x06, 0x30, // per-note pbrange (default = 48) | |||||
0xb0, 0x64, 0x00, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x02 // master pbrange (default = 2) | |||||
}; | |||||
testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); | |||||
} | |||||
{ | |||||
MidiBuffer buffer = MPEMessages::addZone (MPEZone (11, 5, 96, 0)); | |||||
const uint8 expectedBytes[] = | |||||
{ | |||||
0xbb, 0x64, 0x06, 0xbb, 0x65, 0x00, 0xbb, 0x06, 0x05, // set up zone | |||||
0xbb, 0x64, 0x00, 0xbb, 0x65, 0x00, 0xbb, 0x06, 0x60, // per-note pbrange (custom) | |||||
0xba, 0x64, 0x00, 0xba, 0x65, 0x00, 0xba, 0x06, 0x00 // master pbrange (custom) | |||||
}; | |||||
testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); | |||||
} | |||||
} | |||||
beginTest ("set per-note pitchbend range"); | |||||
{ | |||||
MPEZone zone (3, 7, 96); | |||||
MidiBuffer buffer = MPEMessages::perNotePitchbendRange (zone); | |||||
const uint8 expectedBytes[] = { 0xb3, 0x64, 0x00, 0xb3, 0x65, 0x00, 0xb3, 0x06, 0x60 }; | |||||
testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); | |||||
} | |||||
beginTest ("set master pitchbend range"); | |||||
{ | |||||
MPEZone zone (3, 7, 48, 60); | |||||
MidiBuffer buffer = MPEMessages::masterPitchbendRange (zone); | |||||
const uint8 expectedBytes[] = { 0xb2, 0x64, 0x00, 0xb2, 0x65, 0x00, 0xb2, 0x06, 0x3c }; | |||||
testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); | |||||
} | |||||
beginTest ("clear all zones"); | |||||
{ | |||||
MidiBuffer buffer = MPEMessages::clearAllZones(); | |||||
const uint8 expectedBytes[] = { 0xb0, 0x64, 0x06, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x10 }; | |||||
testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); | |||||
} | |||||
beginTest ("set complete state"); | |||||
{ | |||||
MPEZoneLayout layout; | |||||
layout.addZone (MPEZone (1, 7, 96, 0)); | |||||
layout.addZone (MPEZone (9, 7)); | |||||
layout.addZone (MPEZone (5, 3)); | |||||
layout.addZone (MPEZone (5, 4)); | |||||
layout.addZone (MPEZone (6, 4)); | |||||
MidiBuffer buffer = MPEMessages::setZoneLayout (layout); | |||||
const uint8 expectedBytes[] = { | |||||
0xb0, 0x64, 0x06, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x10, // clear all zones | |||||
0xb1, 0x64, 0x06, 0xb1, 0x65, 0x00, 0xb1, 0x06, 0x03, // set zone 1 (1, 3) | |||||
0xb1, 0x64, 0x00, 0xb1, 0x65, 0x00, 0xb1, 0x06, 0x60, // per-note pbrange (custom) | |||||
0xb0, 0x64, 0x00, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x00, // master pbrange (custom) | |||||
0xb6, 0x64, 0x06, 0xb6, 0x65, 0x00, 0xb6, 0x06, 0x04, // set zone 2 (6, 4) | |||||
0xb6, 0x64, 0x00, 0xb6, 0x65, 0x00, 0xb6, 0x06, 0x30, // per-note pbrange (default = 48) | |||||
0xb5, 0x64, 0x00, 0xb5, 0x65, 0x00, 0xb5, 0x06, 0x02 // master pbrange (default = 2) | |||||
}; | |||||
testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); | |||||
} | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
void testMidiBuffer (MidiBuffer& buffer, const uint8* expectedBytes, int expectedBytesSize) | |||||
{ | |||||
uint8 actualBytes[128] = { 0 }; | |||||
extractRawBinaryData (buffer, actualBytes, sizeof (actualBytes)); | |||||
expectEquals (std::memcmp (actualBytes, expectedBytes, (std::size_t) expectedBytesSize), 0); | |||||
} | |||||
//============================================================================== | |||||
void extractRawBinaryData (const MidiBuffer& midiBuffer, const uint8* bufferToCopyTo, std::size_t maxBytes) | |||||
{ | |||||
std::size_t pos = 0; | |||||
MidiBuffer::Iterator iter (midiBuffer); | |||||
MidiMessage midiMessage; | |||||
int samplePosition; // Note: not actually used, so no need to initialise. | |||||
while (iter.getNextEvent (midiMessage, samplePosition)) | |||||
{ | |||||
const uint8* data = midiMessage.getRawData(); | |||||
std::size_t dataSize = (std::size_t) midiMessage.getRawDataSize(); | |||||
if (pos + dataSize > maxBytes) | |||||
return; | |||||
std::memcpy ((void*) (bufferToCopyTo + pos), data, dataSize); | |||||
pos += dataSize; | |||||
} | |||||
} | |||||
}; | |||||
static MPEMessagesTests MPEMessagesUnitTests; | |||||
#endif // JUCE_UNIT_TESTS | |||||
} // namespace juce |
@@ -0,0 +1,91 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
This helper class contains the necessary helper functions to generate | |||||
MIDI messages that are exclusive to MPE, such as defining | |||||
MPE zones and setting per-note and master pitchbend ranges. | |||||
You can then send them to your MPE device using | |||||
MidiOutput::sendBlockOfMessagesNow. | |||||
All other MPE messages like per-note pitchbend, pressure, and third | |||||
dimension, are ordinary MIDI messages that should be created using the MidiMessage | |||||
class instead. You just need to take care to send them to the appropriate | |||||
per-note MIDI channel. | |||||
Note: if you are working with an MPEZoneLayout object inside your app, | |||||
you should not use the message sequences provided here. Instead, you should | |||||
change the zone layout programmatically with the member functions provided in the | |||||
MPEZoneLayout class itself. You should also make sure that the Expressive | |||||
MIDI zone layout of your C++ code and of the MPE device are kept in sync. | |||||
@see MidiMessage, MPEZoneLayout, MPEZone | |||||
*/ | |||||
class JUCE_API MPEMessages | |||||
{ | |||||
public: | |||||
/** Returns the sequence of MIDI messages that, if sent to an Expressive | |||||
MIDI device, will define a new MPE zone. | |||||
*/ | |||||
static MidiBuffer addZone (MPEZone zone); | |||||
/** Returns the sequence of MIDI messages that, if sent to an Expressive | |||||
MIDI device, will change the per-note pitchbend range of an | |||||
existing MPE zone. | |||||
*/ | |||||
static MidiBuffer perNotePitchbendRange (MPEZone zone); | |||||
/** Returns the sequence of MIDI messages that, if sent to an Expressive | |||||
MIDI device, will change the master pitchbend range of an | |||||
existing MPE zone. | |||||
*/ | |||||
static MidiBuffer masterPitchbendRange (MPEZone zone); | |||||
/** Returns the sequence of MIDI messages that, if sent to an Expressive | |||||
MIDI device, will erase all currently defined MPE zones. | |||||
*/ | |||||
static MidiBuffer clearAllZones(); | |||||
/** Returns the sequence of MIDI messages that, if sent to an Expressive | |||||
MIDI device, will reset the whole MPE zone layout of the | |||||
device to the laoyut passed in. This will first clear all currently | |||||
defined MPE zones, then add all zones contained in the | |||||
passed-in zone layout, and set their per-note and master pitchbend | |||||
ranges to their current values. | |||||
*/ | |||||
static MidiBuffer setZoneLayout (const MPEZoneLayout& layout); | |||||
/** The RPN number used for MPE zone layout messages. | |||||
Note: This number can change in later versions of MPE. | |||||
Pitchbend range messages (both per-note and master) are instead sent | |||||
on RPN 0 as in standard MIDI 1.0. | |||||
*/ | |||||
static const int zoneLayoutMessagesRpnNumber = 6; | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,135 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
namespace | |||||
{ | |||||
uint16 generateNoteID (int midiChannel, int midiNoteNumber) noexcept | |||||
{ | |||||
jassert (midiChannel > 0 && midiChannel <= 16); | |||||
jassert (midiNoteNumber >= 0 && midiNoteNumber < 128); | |||||
return uint16 ((midiChannel << 7) + midiNoteNumber); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
MPENote::MPENote (int midiChannel_, | |||||
int initialNote_, | |||||
MPEValue noteOnVelocity_, | |||||
MPEValue pitchbend_, | |||||
MPEValue pressure_, | |||||
MPEValue timbre_, | |||||
KeyState keyState_) noexcept | |||||
: noteID (generateNoteID (midiChannel_, initialNote_)), | |||||
midiChannel (uint8 (midiChannel_)), | |||||
initialNote (uint8 (initialNote_)), | |||||
noteOnVelocity (noteOnVelocity_), | |||||
pitchbend (pitchbend_), | |||||
pressure (pressure_), | |||||
timbre (timbre_), | |||||
noteOffVelocity (MPEValue::minValue()), | |||||
keyState (keyState_) | |||||
{ | |||||
jassert (keyState != MPENote::off); | |||||
jassert (isValid()); | |||||
} | |||||
MPENote::MPENote() noexcept | |||||
: noteID (0), | |||||
midiChannel (0), | |||||
initialNote (0), | |||||
noteOnVelocity (MPEValue::minValue()), | |||||
pitchbend (MPEValue::centreValue()), | |||||
pressure (MPEValue::centreValue()), | |||||
timbre (MPEValue::centreValue()), | |||||
noteOffVelocity (MPEValue::minValue()), | |||||
keyState (MPENote::off) | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
bool MPENote::isValid() const noexcept | |||||
{ | |||||
return midiChannel > 0 && midiChannel <= 16 && initialNote < 128; | |||||
} | |||||
//============================================================================== | |||||
double MPENote::getFrequencyInHertz (double frequencyOfA) const noexcept | |||||
{ | |||||
double pitchInSemitones = double (initialNote) + totalPitchbendInSemitones; | |||||
return frequencyOfA * std::pow (2.0, (pitchInSemitones - 69.0) / 12.0); | |||||
} | |||||
//============================================================================== | |||||
bool MPENote::operator== (const MPENote& other) const noexcept | |||||
{ | |||||
jassert (isValid() && other.isValid()); | |||||
return noteID == other.noteID; | |||||
} | |||||
bool MPENote::operator!= (const MPENote& other) const noexcept | |||||
{ | |||||
jassert (isValid() && other.isValid()); | |||||
return noteID != other.noteID; | |||||
} | |||||
//============================================================================== | |||||
//============================================================================== | |||||
#if JUCE_UNIT_TESTS | |||||
class MPENoteTests : public UnitTest | |||||
{ | |||||
public: | |||||
MPENoteTests() : UnitTest ("MPENote class", "MIDI/MPE") {} | |||||
//============================================================================== | |||||
void runTest() override | |||||
{ | |||||
beginTest ("getFrequencyInHertz"); | |||||
{ | |||||
MPENote note; | |||||
note.initialNote = 60; | |||||
note.totalPitchbendInSemitones = -0.5; | |||||
expectEqualsWithinOneCent (note.getFrequencyInHertz(), 254.178); | |||||
} | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
void expectEqualsWithinOneCent (double frequencyInHertzActual, | |||||
double frequencyInHertzExpected) | |||||
{ | |||||
double ratio = frequencyInHertzActual / frequencyInHertzExpected; | |||||
double oneCent = 1.0005946; | |||||
expect (ratio < oneCent); | |||||
expect (ratio > 1.0 / oneCent); | |||||
} | |||||
}; | |||||
static MPENoteTests MPENoteUnitTests; | |||||
#endif // JUCE_UNIT_TESTS | |||||
} // namespace juce |
@@ -0,0 +1,176 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
This struct represents a playing MPE note. | |||||
A note is identified by a unique ID, or alternatively, by a MIDI channel | |||||
and an initial note. It is characterised by five dimensions of continuous | |||||
expressive control. Their current values are represented as | |||||
MPEValue objects. | |||||
@see MPEValue | |||||
*/ | |||||
struct JUCE_API MPENote | |||||
{ | |||||
//============================================================================== | |||||
enum KeyState | |||||
{ | |||||
off = 0, | |||||
keyDown = 1, | |||||
sustained = 2, | |||||
keyDownAndSustained = 3 | |||||
}; | |||||
//============================================================================== | |||||
/** Constructor. | |||||
@param midiChannel The MIDI channel of the note, between 2 and 16. | |||||
(Channel 1 can never be a note channel in MPE). | |||||
@param initialNote The MIDI note number, between 0 and 127. | |||||
@param velocity The note-on velocity of the note. | |||||
@param pitchbend The initial per-note pitchbend of the note. | |||||
@param pressure The initial pressure of the note. | |||||
@param timbre The timbre value of the note. | |||||
@param keyState The key state of the note (whether the key is down | |||||
and/or the note is sustained). This value must not | |||||
be MPENote::off, since you are triggering a new note. | |||||
(If not specified, the default value will be MPENOte::keyDown.) | |||||
*/ | |||||
MPENote (int midiChannel, | |||||
int initialNote, | |||||
MPEValue velocity, | |||||
MPEValue pitchbend, | |||||
MPEValue pressure, | |||||
MPEValue timbre, | |||||
KeyState keyState = MPENote::keyDown) noexcept; | |||||
/** Default constructor. | |||||
Constructs an invalid MPE note (a note with the key state MPENote::off | |||||
and an invalid MIDI channel. The only allowed use for such a note is to | |||||
call isValid() on it; everything else is undefined behaviour. | |||||
*/ | |||||
MPENote() noexcept; | |||||
/** Checks whether the MPE note is valid. */ | |||||
bool isValid() const noexcept; | |||||
//============================================================================== | |||||
// Invariants that define the note. | |||||
/** A unique ID. Useful to distinguish the note from other simultaneously | |||||
sounding notes that may use the same note number or MIDI channel. | |||||
This should never change during the lifetime of a note object. | |||||
*/ | |||||
uint16 noteID; | |||||
/** The MIDI channel which this note uses. | |||||
This should never change during the lifetime of an MPENote object. | |||||
*/ | |||||
uint8 midiChannel; | |||||
/** The MIDI note number that was sent when the note was triggered. | |||||
This should never change during the lifetime of an MPENote object. | |||||
*/ | |||||
uint8 initialNote; | |||||
//============================================================================== | |||||
// The five dimensions of continuous expressive control | |||||
/** The velocity ("strike") of the note-on. | |||||
This dimension will stay constant after the note has been turned on. | |||||
*/ | |||||
MPEValue noteOnVelocity; | |||||
/** Current per-note pitchbend of the note (in units of MIDI pitchwheel | |||||
position). This dimension can be modulated while the note sounds. | |||||
Note: This value is not aware of the currently used pitchbend range, | |||||
or an additional master pitchbend that may be simultaneously applied. | |||||
To compute the actual effective pitchbend of an MPENote, you should | |||||
probably use the member totalPitchbendInSemitones instead. | |||||
@see totalPitchbendInSemitones, getFrequencyInHertz | |||||
*/ | |||||
MPEValue pitchbend; | |||||
/** Current pressure with which the note is held down. | |||||
This dimension can be modulated while the note sounds. | |||||
*/ | |||||
MPEValue pressure; | |||||
/** Current value of the note's third expressive dimension, tyically | |||||
encoding some kind of timbre parameter. | |||||
This dimension can be modulated while the note sounds. | |||||
*/ | |||||
MPEValue timbre; | |||||
/** The release velocity ("lift") of the note after a note-off has been | |||||
received. | |||||
This dimension will only have a meaningful value after a note-off has | |||||
been received for the note (and keyState is set to MPENote::off or | |||||
MPENOte::sustained). Initially, the value is undefined. | |||||
*/ | |||||
MPEValue noteOffVelocity; | |||||
//============================================================================== | |||||
/** Current effective pitchbend of the note in units of semitones, relative | |||||
to initialNote. You should use this to compute the actual effective pitch | |||||
of the note. This value is computed and set by an MPEInstrument to the | |||||
sum of the per-note pitchbend value (stored in MPEValue::pitchbend) | |||||
and the master pitchbend of the MPE zone, weighted with the per-note | |||||
pitchbend range and master pitchbend range of the zone, respectively. | |||||
@see getFrequencyInHertz | |||||
*/ | |||||
double totalPitchbendInSemitones; | |||||
/** Current key state. Indicates whether the note key is currently down (pressed) | |||||
and/or the note is sustained (by a sustain or sostenuto pedal). | |||||
*/ | |||||
KeyState keyState; | |||||
//============================================================================== | |||||
/** Returns the current frequency of the note in Hertz. This is the a sum of | |||||
the initialNote and the totalPitchbendInSemitones, converted to Hertz. | |||||
*/ | |||||
double getFrequencyInHertz (double frequencyOfA = 440.0) const noexcept; | |||||
/** Returns true if two notes are the same, determined by their unique ID. */ | |||||
bool operator== (const MPENote& other) const noexcept; | |||||
/** Returns true if two notes are different notes, determined by their unique ID. */ | |||||
bool operator!= (const MPENote& other) const noexcept; | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,359 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
MPESynthesiser::MPESynthesiser() | |||||
{ | |||||
} | |||||
MPESynthesiser::MPESynthesiser (MPEInstrument* mpeInstrument) : MPESynthesiserBase (mpeInstrument) | |||||
{ | |||||
} | |||||
MPESynthesiser::~MPESynthesiser() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
void MPESynthesiser::startVoice (MPESynthesiserVoice* voice, MPENote noteToStart) | |||||
{ | |||||
jassert (voice != nullptr); | |||||
voice->currentlyPlayingNote = noteToStart; | |||||
voice->noteStarted(); | |||||
} | |||||
void MPESynthesiser::stopVoice (MPESynthesiserVoice* voice, MPENote noteToStop, bool allowTailOff) | |||||
{ | |||||
jassert (voice != nullptr); | |||||
voice->currentlyPlayingNote = noteToStop; | |||||
voice->noteStopped (allowTailOff); | |||||
} | |||||
//============================================================================== | |||||
void MPESynthesiser::noteAdded (MPENote newNote) | |||||
{ | |||||
const ScopedLock sl (voicesLock); | |||||
if (MPESynthesiserVoice* voice = findFreeVoice (newNote, shouldStealVoices)) | |||||
startVoice (voice, newNote); | |||||
} | |||||
void MPESynthesiser::notePressureChanged (MPENote changedNote) | |||||
{ | |||||
const ScopedLock sl (voicesLock); | |||||
for (int i = 0; i < voices.size(); ++i) | |||||
{ | |||||
MPESynthesiserVoice* voice = voices.getUnchecked (i); | |||||
if (voice->isCurrentlyPlayingNote (changedNote)) | |||||
{ | |||||
voice->currentlyPlayingNote = changedNote; | |||||
voice->notePressureChanged(); | |||||
} | |||||
} | |||||
} | |||||
void MPESynthesiser::notePitchbendChanged (MPENote changedNote) | |||||
{ | |||||
const ScopedLock sl (voicesLock); | |||||
for (int i = 0; i < voices.size(); ++i) | |||||
{ | |||||
MPESynthesiserVoice* voice = voices.getUnchecked (i); | |||||
if (voice->isCurrentlyPlayingNote (changedNote)) | |||||
{ | |||||
voice->currentlyPlayingNote = changedNote; | |||||
voice->notePitchbendChanged(); | |||||
} | |||||
} | |||||
} | |||||
void MPESynthesiser::noteTimbreChanged (MPENote changedNote) | |||||
{ | |||||
const ScopedLock sl (voicesLock); | |||||
for (int i = 0; i < voices.size(); ++i) | |||||
{ | |||||
MPESynthesiserVoice* voice = voices.getUnchecked (i); | |||||
if (voice->isCurrentlyPlayingNote (changedNote)) | |||||
{ | |||||
voice->currentlyPlayingNote = changedNote; | |||||
voice->noteTimbreChanged(); | |||||
} | |||||
} | |||||
} | |||||
void MPESynthesiser::noteKeyStateChanged (MPENote changedNote) | |||||
{ | |||||
const ScopedLock sl (voicesLock); | |||||
for (int i = 0; i < voices.size(); ++i) | |||||
{ | |||||
MPESynthesiserVoice* voice = voices.getUnchecked (i); | |||||
if (voice->isCurrentlyPlayingNote (changedNote)) | |||||
{ | |||||
voice->currentlyPlayingNote = changedNote; | |||||
voice->noteKeyStateChanged(); | |||||
} | |||||
} | |||||
} | |||||
void MPESynthesiser::noteReleased (MPENote finishedNote) | |||||
{ | |||||
const ScopedLock sl (voicesLock); | |||||
for (int i = voices.size(); --i >= 0;) | |||||
{ | |||||
MPESynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
if (voice->isCurrentlyPlayingNote(finishedNote)) | |||||
stopVoice (voice, finishedNote, true); | |||||
} | |||||
} | |||||
void MPESynthesiser::setCurrentPlaybackSampleRate (const double newRate) | |||||
{ | |||||
MPESynthesiserBase::setCurrentPlaybackSampleRate (newRate); | |||||
const ScopedLock sl (voicesLock); | |||||
turnOffAllVoices (false); | |||||
for (int i = voices.size(); --i >= 0;) | |||||
voices.getUnchecked (i)->setCurrentSampleRate (newRate); | |||||
} | |||||
void MPESynthesiser::handleMidiEvent (const MidiMessage& m) | |||||
{ | |||||
if (m.isController()) | |||||
handleController (m.getChannel(), m.getControllerNumber(), m.getControllerValue()); | |||||
else if (m.isProgramChange()) | |||||
handleProgramChange (m.getChannel(), m.getProgramChangeNumber()); | |||||
MPESynthesiserBase::handleMidiEvent (m); | |||||
} | |||||
MPESynthesiserVoice* MPESynthesiser::findFreeVoice (MPENote noteToFindVoiceFor, bool stealIfNoneAvailable) const | |||||
{ | |||||
const ScopedLock sl (voicesLock); | |||||
for (int i = 0; i < voices.size(); ++i) | |||||
{ | |||||
MPESynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
if (! voice->isActive()) | |||||
return voice; | |||||
} | |||||
if (stealIfNoneAvailable) | |||||
return findVoiceToSteal (noteToFindVoiceFor); | |||||
return nullptr; | |||||
} | |||||
struct MPEVoiceAgeSorter | |||||
{ | |||||
static int compareElements (MPESynthesiserVoice* v1, MPESynthesiserVoice* v2) noexcept | |||||
{ | |||||
return v1->wasStartedBefore (*v2) ? -1 : (v2->wasStartedBefore (*v1) ? 1 : 0); | |||||
} | |||||
}; | |||||
MPESynthesiserVoice* MPESynthesiser::findVoiceToSteal (MPENote noteToStealVoiceFor) const | |||||
{ | |||||
// This voice-stealing algorithm applies the following heuristics: | |||||
// - Re-use the oldest notes first | |||||
// - Protect the lowest & topmost notes, even if sustained, but not if they've been released. | |||||
// apparently you are trying to render audio without having any voices... | |||||
jassert (voices.size() > 0); | |||||
// These are the voices we want to protect (ie: only steal if unavoidable) | |||||
MPESynthesiserVoice* low = nullptr; // Lowest sounding note, might be sustained, but NOT in release phase | |||||
MPESynthesiserVoice* top = nullptr; // Highest sounding note, might be sustained, but NOT in release phase | |||||
// this is a list of voices we can steal, sorted by how long they've been running | |||||
Array<MPESynthesiserVoice*> usableVoices; | |||||
usableVoices.ensureStorageAllocated (voices.size()); | |||||
for (int i = 0; i < voices.size(); ++i) | |||||
{ | |||||
MPESynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
jassert (voice->isActive()); // We wouldn't be here otherwise | |||||
MPEVoiceAgeSorter sorter; | |||||
usableVoices.addSorted (sorter, voice); | |||||
if (! voice->isPlayingButReleased()) // Don't protect released notes | |||||
{ | |||||
const int noteNumber = voice->getCurrentlyPlayingNote().initialNote; | |||||
if (low == nullptr || noteNumber < low->getCurrentlyPlayingNote().initialNote) | |||||
low = voice; | |||||
if (top == nullptr || noteNumber > top->getCurrentlyPlayingNote().initialNote) | |||||
top = voice; | |||||
} | |||||
} | |||||
// Eliminate pathological cases (ie: only 1 note playing): we always give precedence to the lowest note(s) | |||||
if (top == low) | |||||
top = nullptr; | |||||
const int numUsableVoices = usableVoices.size(); | |||||
// If we want to re-use the voice to trigger a new note, | |||||
// then The oldest note that's playing the same note number is ideal. | |||||
if (noteToStealVoiceFor.isValid()) | |||||
{ | |||||
for (int i = 0; i < numUsableVoices; ++i) | |||||
{ | |||||
MPESynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||||
if (voice->getCurrentlyPlayingNote().initialNote == noteToStealVoiceFor.initialNote) | |||||
return voice; | |||||
} | |||||
} | |||||
// Oldest voice that has been released (no finger on it and not held by sustain pedal) | |||||
for (int i = 0; i < numUsableVoices; ++i) | |||||
{ | |||||
MPESynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||||
if (voice != low && voice != top && voice->isPlayingButReleased()) | |||||
return voice; | |||||
} | |||||
// Oldest voice that doesn't have a finger on it: | |||||
for (int i = 0; i < numUsableVoices; ++i) | |||||
{ | |||||
MPESynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||||
if (voice != low && voice != top | |||||
&& voice->getCurrentlyPlayingNote().keyState != MPENote::keyDown | |||||
&& voice->getCurrentlyPlayingNote().keyState != MPENote::keyDownAndSustained) | |||||
return voice; | |||||
} | |||||
// Oldest voice that isn't protected | |||||
for (int i = 0; i < numUsableVoices; ++i) | |||||
{ | |||||
MPESynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||||
if (voice != low && voice != top) | |||||
return voice; | |||||
} | |||||
// We've only got "protected" voices now: lowest note takes priority | |||||
jassert (low != nullptr); | |||||
// Duophonic synth: give priority to the bass note: | |||||
if (top != nullptr) | |||||
return top; | |||||
return low; | |||||
} | |||||
//============================================================================== | |||||
void MPESynthesiser::addVoice (MPESynthesiserVoice* const newVoice) | |||||
{ | |||||
const ScopedLock sl (voicesLock); | |||||
newVoice->setCurrentSampleRate (getSampleRate()); | |||||
voices.add (newVoice); | |||||
} | |||||
void MPESynthesiser::clearVoices() | |||||
{ | |||||
const ScopedLock sl (voicesLock); | |||||
voices.clear(); | |||||
} | |||||
MPESynthesiserVoice* MPESynthesiser::getVoice (const int index) const | |||||
{ | |||||
const ScopedLock sl (voicesLock); | |||||
return voices [index]; | |||||
} | |||||
void MPESynthesiser::removeVoice (const int index) | |||||
{ | |||||
const ScopedLock sl (voicesLock); | |||||
voices.remove (index); | |||||
} | |||||
void MPESynthesiser::reduceNumVoices (const int newNumVoices) | |||||
{ | |||||
// we can't possibly get to a negative number of voices... | |||||
jassert (newNumVoices >= 0); | |||||
const ScopedLock sl (voicesLock); | |||||
while (voices.size() > newNumVoices) | |||||
{ | |||||
if (MPESynthesiserVoice* voice = findFreeVoice (MPENote(), true)) | |||||
voices.removeObject (voice); | |||||
else | |||||
voices.remove (0); // if there's no voice to steal, kill the oldest voice | |||||
} | |||||
} | |||||
void MPESynthesiser::turnOffAllVoices (bool allowTailOff) | |||||
{ | |||||
// first turn off all voices (it's more efficient to do this immediately | |||||
// rather than to go through the MPEInstrument for this). | |||||
for (int i = voices.size(); --i >= 0;) | |||||
voices.getUnchecked (i)->noteStopped (allowTailOff); | |||||
// finally make sure the MPE Instrument also doesn't have any notes anymore. | |||||
instrument->releaseAllNotes(); | |||||
} | |||||
//============================================================================== | |||||
void MPESynthesiser::renderNextSubBlock (AudioBuffer<float>& buffer, int startSample, int numSamples) | |||||
{ | |||||
for (int i = voices.size(); --i >= 0;) | |||||
{ | |||||
MPESynthesiserVoice* voice = voices.getUnchecked (i); | |||||
if (voice->isActive()) | |||||
voice->renderNextBlock (buffer, startSample, numSamples); | |||||
} | |||||
} | |||||
void MPESynthesiser::renderNextSubBlock (AudioBuffer<double>& buffer, int startSample, int numSamples) | |||||
{ | |||||
for (int i = voices.size(); --i >= 0;) | |||||
{ | |||||
MPESynthesiserVoice* voice = voices.getUnchecked (i); | |||||
if (voice->isActive()) | |||||
voice->renderNextBlock (buffer, startSample, numSamples); | |||||
} | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,309 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Base class for an MPE-compatible musical device that can play sounds. | |||||
This class extends MPESynthesiserBase by adding the concept of voices, | |||||
each of which can play a sound triggered by a MPENote that can be modulated | |||||
by MPE dimensions like pressure, pitchbend, and timbre, while the note is | |||||
sounding. | |||||
To create a synthesiser, you'll need to create a subclass of MPESynthesiserVoice | |||||
which can play back one of these sounds at a time. | |||||
Then you can use the addVoice() methods to give the synthesiser a set of voices | |||||
it can use to play notes. 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 (inherited | |||||
from MPESynthesiserBase). The voices will be started, stopped, and modulated | |||||
automatically, based on the MPE/MIDI messages that the synthesiser receives. | |||||
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. | |||||
@see MPESynthesiserBase, MPESythesiserVoice, MPENote, MPEInstrument | |||||
*/ | |||||
class JUCE_API MPESynthesiser : public MPESynthesiserBase | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Constructor. | |||||
You'll need to add some voices before it'll make any sound. | |||||
@see addVoice | |||||
*/ | |||||
MPESynthesiser(); | |||||
/** Constructor to pass to the synthesiser a custom MPEInstrument object | |||||
to handle the MPE note state, MIDI channel assignment etc. | |||||
(in case you need custom logic for this that goes beyond MIDI and MPE). | |||||
The synthesiser will take ownership of this object. | |||||
@see MPESynthesiserBase, MPEInstrument | |||||
*/ | |||||
MPESynthesiser (MPEInstrument* instrument); | |||||
/** Destructor. */ | |||||
~MPESynthesiser(); | |||||
//============================================================================== | |||||
/** Deletes all voices. */ | |||||
void clearVoices(); | |||||
/** Returns the number of voices that have been added. */ | |||||
int getNumVoices() const noexcept { return voices.size(); } | |||||
/** Returns one of the voices that have been added. */ | |||||
MPESynthesiserVoice* 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 (MPESynthesiserVoice* newVoice); | |||||
/** Deletes one of the voices. */ | |||||
void removeVoice (int index); | |||||
/** Reduces the number of voices to newNumVoices. | |||||
This will repeatedly call findVoiceToSteal() and remove that voice, until | |||||
the total number of voices equals newNumVoices. If newNumVoices is greater than | |||||
or equal to the current number of voices, this method does nothing. | |||||
*/ | |||||
void reduceNumVoices (int newNumVoices); | |||||
/** Release all MPE notes and turn off all voices. | |||||
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 is meant to be called by the user, for example to implement | |||||
a MIDI panic button in a synth. | |||||
*/ | |||||
virtual void turnOffAllVoices (bool allowTailOff); | |||||
//============================================================================== | |||||
/** 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 setVoiceStealingEnabled (bool shouldSteal) noexcept { shouldStealVoices = shouldSteal; } | |||||
/** Returns true if note-stealing is enabled. */ | |||||
bool isVoiceStealingEnabled() const noexcept { return shouldStealVoices; } | |||||
//============================================================================== | |||||
/** Tells the synthesiser what the sample rate is for the audio it's being used to render. | |||||
This overrides the implementation in MPESynthesiserBase, to additionally | |||||
propagate the new value to the voices so that they can use it to render the correct | |||||
pitches. | |||||
*/ | |||||
void setCurrentPlaybackSampleRate (double newRate) override; | |||||
//============================================================================== | |||||
/** Handle incoming MIDI events. | |||||
This method will be called automatically according to the MIDI data passed | |||||
into renderNextBlock(), but you can also call it yourself to manually | |||||
inject MIDI events. | |||||
This implementation forwards program change messages and non-MPE-related | |||||
controller messages to handleProgramChange and handleController, respectively, | |||||
and then simply calls through to MPESynthesiserBase::handleMidiEvent to deal | |||||
with MPE-related MIDI messages used for MPE notes, zones etc. | |||||
This method can be overridden further if you need to do custom MIDI | |||||
handling on top of what is provided here. | |||||
*/ | |||||
void handleMidiEvent (const MidiMessage&) override; | |||||
/** Callback for MIDI controller messages. The default implementation | |||||
provided here does nothing; override this method if you need custom | |||||
MIDI controller handling on top of MPE. | |||||
This method will be called automatically according to the midi data passed into | |||||
renderNextBlock(). | |||||
*/ | |||||
virtual void handleController (int /*midiChannel*/, | |||||
int /*controllerNumber*/, | |||||
int /*controllerValue*/) {} | |||||
/** Callback for MIDI program change messages. The default implementation | |||||
provided here does nothing; override this method if you need to handle | |||||
those messages. | |||||
This method will be called automatically according to the midi data passed into | |||||
renderNextBlock(). | |||||
*/ | |||||
virtual void handleProgramChange (int /*midiChannel*/, | |||||
int /*programNumber*/) {} | |||||
protected: | |||||
//============================================================================== | |||||
/** Attempts to start playing a new note. | |||||
The default method here will find a free voice that is appropriate for | |||||
playing the given MPENote, and use that voice to start playing the sound. | |||||
If isNoteStealingEnabled returns true (set this by calling setNoteStealingEnabled), | |||||
the synthesiser will use the voice stealing algorithm to find a free voice for | |||||
the note (if no voices are free otherwise). | |||||
This method will be called automatically according to the midi data passed into | |||||
renderNextBlock(). Do not call it yourself, otherwise the internal MPE note state | |||||
will become inconsistent. | |||||
*/ | |||||
virtual void noteAdded (MPENote newNote) override; | |||||
/** Stops playing a note. | |||||
This will be called whenever an MPE note is released (either by a note-off message, | |||||
or by a sustain/sostenuto pedal release for a note that already received a note-off), | |||||
and should therefore stop playing. | |||||
This will find any voice that is currently playing finishedNote, | |||||
turn its currently playing note off, and call its noteStopped callback. | |||||
This method will be called automatically according to the midi data passed into | |||||
renderNextBlock(). Do not call it yourself, otherwise the internal MPE note state | |||||
will become inconsistent. | |||||
*/ | |||||
virtual void noteReleased (MPENote finishedNote) override; | |||||
/** Will find any voice that is currently playing changedNote, update its | |||||
currently playing note, and call its notePressureChanged method. | |||||
This method will be called automatically according to the midi data passed into | |||||
renderNextBlock(). Do not call it yourself. | |||||
*/ | |||||
virtual void notePressureChanged (MPENote changedNote) override; | |||||
/** Will find any voice that is currently playing changedNote, update its | |||||
currently playing note, and call its notePitchbendChanged method. | |||||
This method will be called automatically according to the midi data passed into | |||||
renderNextBlock(). Do not call it yourself. | |||||
*/ | |||||
virtual void notePitchbendChanged (MPENote changedNote) override; | |||||
/** Will find any voice that is currently playing changedNote, update its | |||||
currently playing note, and call its noteTimbreChanged method. | |||||
This method will be called automatically according to the midi data passed into | |||||
renderNextBlock(). Do not call it yourself. | |||||
*/ | |||||
virtual void noteTimbreChanged (MPENote changedNote) override; | |||||
/** Will find any voice that is currently playing changedNote, update its | |||||
currently playing note, and call its noteKeyStateChanged method. | |||||
This method will be called automatically according to the midi data passed into | |||||
renderNextBlock(). Do not call it yourself. | |||||
*/ | |||||
virtual void noteKeyStateChanged (MPENote changedNote) override; | |||||
//============================================================================== | |||||
/** This will simply call renderNextBlock for each currently active | |||||
voice and fill the buffer with the sum. | |||||
Override this method if you need to do more work to render your audio. | |||||
*/ | |||||
virtual void renderNextSubBlock (AudioBuffer<float>& outputAudio, | |||||
int startSample, | |||||
int numSamples) override; | |||||
/** This will simply call renderNextBlock for each currently active | |||||
voice and fill the buffer with the sum. (souble-precision version) | |||||
Override this method if you need to do more work to render your audio. | |||||
*/ | |||||
virtual void renderNextSubBlock (AudioBuffer<double>& outputAudio, | |||||
int startSample, | |||||
int numSamples) override; | |||||
//============================================================================== | |||||
/** Searches through the voices to find one that's not currently playing, and | |||||
which can play the given MPE note. | |||||
If all voices are active and stealIfNoneAvailable is false, this returns | |||||
a nullptr. If all voices are active and stealIfNoneAvailable is true, | |||||
this will call findVoiceToSteal() to find a voice. | |||||
If you need to find a free voice for something else than playing a note | |||||
(e.g. for deleting it), you can pass an invalid (default-constructed) MPENote. | |||||
*/ | |||||
virtual MPESynthesiserVoice* findFreeVoice (MPENote noteToFindVoiceFor, | |||||
bool stealIfNoneAvailable) const; | |||||
/** Chooses a voice that is most suitable for being re-used to play a new | |||||
note, or for being deleted by reduceNumVoices. | |||||
The default method will attempt to find the oldest voice that isn't the | |||||
bottom or top note being played. If that's not suitable for your synth, | |||||
you can override this method and do something more cunning instead. | |||||
If you pass a valid MPENote for the optional argument, then the note number | |||||
of that note will be taken into account for finding the ideal voice to steal. | |||||
If you pass an invalid (default-constructed) MPENote instead, this part of | |||||
the algorithm will be ignored. | |||||
*/ | |||||
virtual MPESynthesiserVoice* findVoiceToSteal (MPENote noteToStealVoiceFor = MPENote()) const; | |||||
/** Starts a specified voice and tells it to play a particular MPENote. | |||||
You should never need to call this, it's called internally by | |||||
MPESynthesiserBase::instrument via the noteStarted callback, | |||||
but is protected in case it's useful for some custom subclasses. | |||||
*/ | |||||
void startVoice (MPESynthesiserVoice* voice, MPENote noteToStart); | |||||
/** Stops a given voice and tells it to stop playing a particular MPENote | |||||
(which should be the same note it is actually playing). | |||||
You should never need to call this, it's called internally by | |||||
MPESynthesiserBase::instrument via the noteReleased callback, | |||||
but is protected in case it's useful for some custom subclasses. | |||||
*/ | |||||
void stopVoice (MPESynthesiserVoice* voice, MPENote noteToStop, bool allowTailOff); | |||||
//============================================================================== | |||||
OwnedArray<MPESynthesiserVoice> voices; | |||||
private: | |||||
//============================================================================== | |||||
bool shouldStealVoices; | |||||
CriticalSection voicesLock; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiser) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,185 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
MPESynthesiserBase::MPESynthesiserBase() | |||||
: instrument (new MPEInstrument), | |||||
sampleRate (0), | |||||
minimumSubBlockSize (32), | |||||
subBlockSubdivisionIsStrict (false) | |||||
{ | |||||
instrument->addListener (this); | |||||
} | |||||
MPESynthesiserBase::MPESynthesiserBase (MPEInstrument* inst) | |||||
: instrument (inst), | |||||
sampleRate (0), | |||||
minimumSubBlockSize (32) | |||||
{ | |||||
jassert (instrument != nullptr); | |||||
instrument->addListener (this); | |||||
} | |||||
//============================================================================== | |||||
MPEZoneLayout MPESynthesiserBase::getZoneLayout() const noexcept | |||||
{ | |||||
return instrument->getZoneLayout(); | |||||
} | |||||
void MPESynthesiserBase::setZoneLayout (MPEZoneLayout newLayout) | |||||
{ | |||||
instrument->setZoneLayout (newLayout); | |||||
} | |||||
//============================================================================== | |||||
void MPESynthesiserBase::enableLegacyMode (int pitchbendRange, Range<int> channelRange) | |||||
{ | |||||
instrument->enableLegacyMode (pitchbendRange, channelRange); | |||||
} | |||||
bool MPESynthesiserBase::isLegacyModeEnabled() const noexcept | |||||
{ | |||||
return instrument->isLegacyModeEnabled(); | |||||
} | |||||
Range<int> MPESynthesiserBase::getLegacyModeChannelRange() const noexcept | |||||
{ | |||||
return instrument->getLegacyModeChannelRange(); | |||||
} | |||||
void MPESynthesiserBase::setLegacyModeChannelRange (Range<int> channelRange) | |||||
{ | |||||
instrument->setLegacyModeChannelRange (channelRange); | |||||
} | |||||
int MPESynthesiserBase::getLegacyModePitchbendRange() const noexcept | |||||
{ | |||||
return instrument->getLegacyModePitchbendRange(); | |||||
} | |||||
void MPESynthesiserBase::setLegacyModePitchbendRange (int pitchbendRange) | |||||
{ | |||||
instrument->setLegacyModePitchbendRange (pitchbendRange); | |||||
} | |||||
//============================================================================== | |||||
void MPESynthesiserBase::setPressureTrackingMode (TrackingMode modeToUse) | |||||
{ | |||||
instrument->setPressureTrackingMode (modeToUse); | |||||
} | |||||
void MPESynthesiserBase::setPitchbendTrackingMode (TrackingMode modeToUse) | |||||
{ | |||||
instrument->setPitchbendTrackingMode (modeToUse); | |||||
} | |||||
void MPESynthesiserBase::setTimbreTrackingMode (TrackingMode modeToUse) | |||||
{ | |||||
instrument->setTimbreTrackingMode (modeToUse); | |||||
} | |||||
//============================================================================== | |||||
void MPESynthesiserBase::handleMidiEvent (const MidiMessage& m) | |||||
{ | |||||
instrument->processNextMidiEvent (m); | |||||
} | |||||
//============================================================================== | |||||
template <typename floatType> | |||||
void MPESynthesiserBase::renderNextBlock (AudioBuffer<floatType>& outputAudio, | |||||
const MidiBuffer& inputMidi, | |||||
int startSample, | |||||
int numSamples) | |||||
{ | |||||
// you must set the sample rate before using this! | |||||
jassert (sampleRate != 0); | |||||
MidiBuffer::Iterator midiIterator (inputMidi); | |||||
midiIterator.setNextSamplePosition (startSample); | |||||
bool firstEvent = true; | |||||
int midiEventPos; | |||||
MidiMessage m; | |||||
const ScopedLock sl (noteStateLock); | |||||
while (numSamples > 0) | |||||
{ | |||||
if (! midiIterator.getNextEvent (m, midiEventPos)) | |||||
{ | |||||
renderNextSubBlock (outputAudio, startSample, numSamples); | |||||
return; | |||||
} | |||||
const int samplesToNextMidiMessage = midiEventPos - startSample; | |||||
if (samplesToNextMidiMessage >= numSamples) | |||||
{ | |||||
renderNextSubBlock (outputAudio, startSample, numSamples); | |||||
handleMidiEvent (m); | |||||
break; | |||||
} | |||||
if (samplesToNextMidiMessage < ((firstEvent && ! subBlockSubdivisionIsStrict) ? 1 : minimumSubBlockSize)) | |||||
{ | |||||
handleMidiEvent (m); | |||||
continue; | |||||
} | |||||
firstEvent = false; | |||||
renderNextSubBlock (outputAudio, startSample, samplesToNextMidiMessage); | |||||
handleMidiEvent (m); | |||||
startSample += samplesToNextMidiMessage; | |||||
numSamples -= samplesToNextMidiMessage; | |||||
} | |||||
while (midiIterator.getNextEvent (m, midiEventPos)) | |||||
handleMidiEvent (m); | |||||
} | |||||
// explicit instantiation for supported float types: | |||||
template void MPESynthesiserBase::renderNextBlock<float> (AudioBuffer<float>&, const MidiBuffer&, int, int); | |||||
template void MPESynthesiserBase::renderNextBlock<double> (AudioBuffer<double>&, const MidiBuffer&, int, int); | |||||
//============================================================================== | |||||
void MPESynthesiserBase::setCurrentPlaybackSampleRate (const double newRate) | |||||
{ | |||||
if (sampleRate != newRate) | |||||
{ | |||||
const ScopedLock sl (noteStateLock); | |||||
instrument->releaseAllNotes(); | |||||
sampleRate = newRate; | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void MPESynthesiserBase::setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict) noexcept | |||||
{ | |||||
jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1 | |||||
minimumSubBlockSize = numSamples; | |||||
subBlockSubdivisionIsStrict = shouldBeStrict; | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,208 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Derive from this class to create a basic audio generator capable of MPE. | |||||
Implement the callbacks of MPEInstrument::Listener (noteAdded, notePressureChanged | |||||
etc.) to let your audio generator know that MPE notes were triggered, modulated, | |||||
or released. What to do inside them, and how that influences your audio generator, | |||||
is up to you! | |||||
This class uses an instance of MPEInstrument internally to handle the MPE | |||||
note state logic. | |||||
This class is a very low-level base class for an MPE instrument. If you need | |||||
something more sophisticated, have a look at MPESynthesiser. This class extends | |||||
MPESynthesiserBase by adding the concept of voices that can play notes, | |||||
a voice stealing algorithm, and much more. | |||||
@see MPESynthesiser, MPEInstrument | |||||
*/ | |||||
struct JUCE_API MPESynthesiserBase : public MPEInstrument::Listener | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Constructor. */ | |||||
MPESynthesiserBase(); | |||||
/** Constructor. | |||||
If you use this constructor, the synthesiser will take ownership of the | |||||
provided instrument object, and will use it internally to handle the | |||||
MPE note state logic. | |||||
This is useful if you want to use an instance of your own class derived | |||||
from MPEInstrument for the MPE logic. | |||||
*/ | |||||
MPESynthesiserBase (MPEInstrument* instrument); | |||||
//============================================================================== | |||||
/** Returns the synthesiser's internal MPE zone layout. | |||||
This happens by value, to enforce thread-safety and class invariants. | |||||
*/ | |||||
MPEZoneLayout getZoneLayout() const noexcept; | |||||
/** Re-sets the synthesiser's internal MPE zone layout to the one passed in. | |||||
As a side effect, this will discard all currently playing notes, | |||||
call noteReleased for all of them, and disable legacy mode (if previously enabled). | |||||
*/ | |||||
void setZoneLayout (MPEZoneLayout newLayout); | |||||
//============================================================================== | |||||
/** Tells the synthesiser what the sample rate is for the audio it's being | |||||
used to render. | |||||
*/ | |||||
virtual void setCurrentPlaybackSampleRate (double sampleRate); | |||||
/** Returns the current target sample rate at which rendering is being done. | |||||
Subclasses may need to know this so that they can pitch things correctly. | |||||
*/ | |||||
double getSampleRate() const noexcept { return sampleRate; } | |||||
//============================================================================== | |||||
/** Creates the next block of audio output. | |||||
Call this to make sound. This will chop up the AudioBuffer into subBlock | |||||
pieces separated by events in the MIDI buffer, and then call | |||||
processNextSubBlock on each one of them. In between you will get calls | |||||
to noteAdded/Changed/Finished, where you can update parameters that | |||||
depend on those notes to use for your audio rendering. | |||||
*/ | |||||
template <typename floatType> | |||||
void renderNextBlock (AudioBuffer<floatType>& outputAudio, | |||||
const MidiBuffer& inputMidi, | |||||
int startSample, | |||||
int numSamples); | |||||
//============================================================================== | |||||
/** Handle incoming MIDI events (called from renderNextBlock). | |||||
The default implementation provided here simply forwards everything | |||||
to MPEInstrument::processNextMidiEvent, where it is used to update the | |||||
MPE notes, zones etc. MIDI messages not relevant for MPE are ignored. | |||||
This method can be overridden if you need to do custom MIDI handling | |||||
on top of MPE. The MPESynthesiser class overrides this to implement | |||||
callbacks for MIDI program changes and non-MPE-related MIDI controller | |||||
messages. | |||||
*/ | |||||
virtual void handleMidiEvent (const MidiMessage&); | |||||
//============================================================================== | |||||
/** Sets a minimum limit on the size to which audio sub-blocks will be divided when rendering. | |||||
When rendering, the audio blocks that are passed into renderNextBlock() will be split up | |||||
into smaller blocks that lie between all the incoming midi messages, and it is these smaller | |||||
sub-blocks that are rendered with multiple calls to renderVoices(). | |||||
Obviously in a pathological case where there are midi messages on every sample, then | |||||
renderVoices() could be called once per sample and lead to poor performance, so this | |||||
setting allows you to set a lower limit on the block size. | |||||
The default setting is 32, which means that midi messages are accurate to about < 1ms | |||||
accuracy, which is probably fine for most purposes, but you may want to increase or | |||||
decrease this value for your synth. | |||||
If shouldBeStrict is true, the audio sub-blocks will strictly never be smaller than numSamples. | |||||
If shouldBeStrict is false (default), the first audio sub-block in the buffer is allowed | |||||
to be smaller, to make sure that the first MIDI event in a buffer will always be sample-accurate | |||||
(this can sometimes help to avoid quantisation or phasing issues). | |||||
*/ | |||||
void setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict = false) noexcept; | |||||
//============================================================================== | |||||
/** Puts the synthesiser into legacy mode. | |||||
@param pitchbendRange The note pitchbend range in semitones to use when in legacy mode. | |||||
Must be between 0 and 96, otherwise behaviour is undefined. | |||||
The default pitchbend range in legacy mode is +/- 2 semitones. | |||||
@param channelRange The range of MIDI channels to use for notes when in legacy mode. | |||||
The default is to use all MIDI channels (1-16). | |||||
To get out of legacy mode, set a new MPE zone layout using setZoneLayout. | |||||
*/ | |||||
void enableLegacyMode (int pitchbendRange = 2, | |||||
Range<int> channelRange = Range<int> (1, 17)); | |||||
/** Returns true if the instrument is in legacy mode, false otherwise. */ | |||||
bool isLegacyModeEnabled() const noexcept; | |||||
/** Returns the range of MIDI channels (1-16) to be used for notes when in legacy mode. */ | |||||
Range<int> getLegacyModeChannelRange() const noexcept; | |||||
/** Re-sets the range of MIDI channels (1-16) to be used for notes when in legacy mode. */ | |||||
void setLegacyModeChannelRange (Range<int> channelRange); | |||||
/** Returns the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ | |||||
int getLegacyModePitchbendRange() const noexcept; | |||||
/** Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ | |||||
void setLegacyModePitchbendRange (int pitchbendRange); | |||||
//============================================================================== | |||||
typedef MPEInstrument::TrackingMode TrackingMode; | |||||
/** Set the MPE tracking mode for the pressure dimension. */ | |||||
void setPressureTrackingMode (TrackingMode modeToUse); | |||||
/** Set the MPE tracking mode for the pitchbend dimension. */ | |||||
void setPitchbendTrackingMode (TrackingMode modeToUse); | |||||
/** Set the MPE tracking mode for the timbre dimension. */ | |||||
void setTimbreTrackingMode (TrackingMode modeToUse); | |||||
protected: | |||||
//============================================================================== | |||||
/** Implement this method to render your audio inside. | |||||
@see renderNextBlock | |||||
*/ | |||||
virtual void renderNextSubBlock (AudioBuffer<float>& outputAudio, | |||||
int startSample, | |||||
int numSamples) = 0; | |||||
/** Implement this method if you want to render 64-bit audio as well; | |||||
otherwise leave blank. | |||||
*/ | |||||
virtual void renderNextSubBlock (AudioBuffer<double>& /*outputAudio*/, | |||||
int /*startSample*/, | |||||
int /*numSamples*/) {} | |||||
protected: | |||||
//============================================================================== | |||||
/** @internal */ | |||||
ScopedPointer<MPEInstrument> instrument; | |||||
private: | |||||
//============================================================================== | |||||
CriticalSection noteStateLock; | |||||
double sampleRate; | |||||
int minimumSubBlockSize; | |||||
bool subBlockSubdivisionIsStrict; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiserBase) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,56 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
MPESynthesiserVoice::MPESynthesiserVoice() | |||||
: currentSampleRate (0), noteStartTime (0) | |||||
{ | |||||
} | |||||
MPESynthesiserVoice::~MPESynthesiserVoice() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
bool MPESynthesiserVoice::isCurrentlyPlayingNote (MPENote note) const noexcept | |||||
{ | |||||
return isActive() && currentlyPlayingNote.noteID == note.noteID; | |||||
} | |||||
bool MPESynthesiserVoice::isPlayingButReleased() const noexcept | |||||
{ | |||||
return isActive() && currentlyPlayingNote.keyState == MPENote::off; | |||||
} | |||||
bool MPESynthesiserVoice::wasStartedBefore (const MPESynthesiserVoice& other) const noexcept | |||||
{ | |||||
return noteStartTime < other.noteStartTime; | |||||
} | |||||
void MPESynthesiserVoice::clearCurrentNote() noexcept | |||||
{ | |||||
currentlyPlayingNote = MPENote(); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,188 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Represents an MPE voice that an MPESynthesiser can use to play a sound. | |||||
A voice plays a single sound at a time, and a synthesiser holds an array of | |||||
voices so that it can play polyphonically. | |||||
@see MPESynthesiser, MPENote | |||||
*/ | |||||
class JUCE_API MPESynthesiserVoice | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Constructor. */ | |||||
MPESynthesiserVoice(); | |||||
/** Destructor. */ | |||||
virtual ~MPESynthesiserVoice(); | |||||
/** Returns the MPENote that this voice is currently playing. | |||||
Returns an invalid MPENote if no note is playing | |||||
(you can check this using MPENote::isValid() or MPEVoice::isActive()). | |||||
*/ | |||||
MPENote getCurrentlyPlayingNote() const noexcept { return currentlyPlayingNote; } | |||||
/** Returns true if the voice is currently playing the given MPENote | |||||
(as identified by the note's initial note number and MIDI channel). | |||||
*/ | |||||
bool isCurrentlyPlayingNote (MPENote note) const noexcept; | |||||
/** Returns true if this voice is currently busy playing a sound. | |||||
By default this just checks whether getCurrentlyPlayingNote() | |||||
returns a valid MPE note, but can be overridden for more advanced checking. | |||||
*/ | |||||
virtual bool isActive() const { return currentlyPlayingNote.isValid(); } | |||||
/** Returns true if a voice is sounding in its release phase. **/ | |||||
bool isPlayingButReleased() const noexcept; | |||||
/** Called by the MPESynthesiser to let the voice know that a new note has started on it. | |||||
This will be called during the rendering callback, so must be fast and thread-safe. | |||||
*/ | |||||
virtual void noteStarted() = 0; | |||||
/** Called by the MPESynthesiser to let the voice know that its currently playing note has stopped. | |||||
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 noteStopped (bool allowTailOff) = 0; | |||||
/** Called by the MPESynthesiser to let the voice know that its currently playing note | |||||
has changed its pressure value. | |||||
This will be called during the rendering callback, so must be fast and thread-safe. | |||||
*/ | |||||
virtual void notePressureChanged() = 0; | |||||
/** Called by the MPESynthesiser to let the voice know that its currently playing note | |||||
has changed its pitchbend value. | |||||
This will be called during the rendering callback, so must be fast and thread-safe. | |||||
Note: You can call currentlyPlayingNote.getFrequencyInHertz() to find out the effective frequency | |||||
of the note, as a sum of the initial note number, the per-note pitchbend and the master pitchbend. | |||||
*/ | |||||
virtual void notePitchbendChanged() = 0; | |||||
/** Called by the MPESynthesiser to let the voice know that its currently playing note | |||||
has changed its timbre value. | |||||
This will be called during the rendering callback, so must be fast and thread-safe. | |||||
*/ | |||||
virtual void noteTimbreChanged() = 0; | |||||
/** Called by the MPESynthesiser to let the voice know that its currently playing note | |||||
has changed its key state. | |||||
This typically happens when a sustain or sostenuto pedal is pressed or released (on | |||||
an MPE channel relevant for this note), or if the note key is lifted while the sustained | |||||
or sostenuto pedal is still held down. | |||||
This will be called during the rendering callback, so must be fast and thread-safe. | |||||
*/ | |||||
virtual void noteKeyStateChanged() = 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 (AudioBuffer<float>& outputBuffer, | |||||
int startSample, | |||||
int numSamples) = 0; | |||||
/** Renders the next block of 64-bit data for this voice. | |||||
Support for 64-bit audio is optional. You can choose to not override this method if | |||||
you don't need it (the default implementation simply does nothing). | |||||
*/ | |||||
virtual void renderNextBlock (AudioBuffer<double>& /*outputBuffer*/, | |||||
int /*startSample*/, | |||||
int /*numSamples*/) {} | |||||
/** Changes the voice's reference sample rate. | |||||
The rate is set so that subclasses know the output rate and can set their pitch | |||||
accordingly. | |||||
This method is called by the synth, and subclasses can access the current rate with | |||||
the currentSampleRate member. | |||||
*/ | |||||
virtual void setCurrentSampleRate (double newRate) { currentSampleRate = newRate; } | |||||
/** Returns the current target sample rate at which rendering is being done. | |||||
Subclasses may need to know this so that they can pitch things correctly. | |||||
*/ | |||||
double getSampleRate() const noexcept { return currentSampleRate; } | |||||
/** Returns true if this voice started playing its current note before the other voice did. */ | |||||
bool wasStartedBefore (const MPESynthesiserVoice& other) const noexcept; | |||||
protected: | |||||
//============================================================================== | |||||
/** Resets the state of this voice after a sound has finished playing. | |||||
The subclass must call this when it finishes playing a note and becomes available | |||||
to play new ones. | |||||
It must either call it in the stopNote() method, or if the voice is tailing off, | |||||
then it should call it later during the renderNextBlock method, as soon as it | |||||
finishes its tail-off. | |||||
It can also be called at any time during the render callback if the sound happens | |||||
to have finished, e.g. if it's playing a sample and the sample finishes. | |||||
*/ | |||||
void clearCurrentNote() noexcept; | |||||
//============================================================================== | |||||
double currentSampleRate; | |||||
MPENote currentlyPlayingNote; | |||||
private: | |||||
//============================================================================== | |||||
friend class MPESynthesiser; | |||||
uint32 noteStartTime; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiserVoice) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,173 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
MPEValue::MPEValue() noexcept : normalisedValue (8192) | |||||
{ | |||||
} | |||||
MPEValue::MPEValue (int value) : normalisedValue (value) | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
MPEValue MPEValue::from7BitInt (int value) noexcept | |||||
{ | |||||
jassert (value >= 0 && value <= 127); | |||||
const int valueAs14Bit = value <= 64 ? value << 7 : int (jmap<float> (float (value - 64), 0.0f, 63.0f, 0.0f, 8191.0f)) + 8192; | |||||
return MPEValue (valueAs14Bit); | |||||
} | |||||
MPEValue MPEValue::from14BitInt (int value) noexcept | |||||
{ | |||||
jassert (value >= 0 && value <= 16383); | |||||
return MPEValue (value); | |||||
} | |||||
//============================================================================== | |||||
MPEValue MPEValue::minValue() noexcept { return MPEValue::from7BitInt (0); } | |||||
MPEValue MPEValue::centreValue() noexcept { return MPEValue::from7BitInt (64); } | |||||
MPEValue MPEValue::maxValue() noexcept { return MPEValue::from7BitInt (127); } | |||||
int MPEValue::as7BitInt() const noexcept | |||||
{ | |||||
return normalisedValue >> 7; | |||||
} | |||||
int MPEValue::as14BitInt() const noexcept | |||||
{ | |||||
return normalisedValue; | |||||
} | |||||
//============================================================================== | |||||
float MPEValue::asSignedFloat() const noexcept | |||||
{ | |||||
return (normalisedValue < 8192) | |||||
? jmap<float> (float (normalisedValue), 0.0f, 8192.0f, -1.0f, 0.0f) | |||||
: jmap<float> (float (normalisedValue), 8192.0f, 16383.0f, 0.0f, 1.0f); | |||||
} | |||||
float MPEValue::asUnsignedFloat() const noexcept | |||||
{ | |||||
return jmap<float> (float (normalisedValue), 0.0f, 16383.0f, 0.0f, 1.0f); | |||||
} | |||||
//============================================================================== | |||||
bool MPEValue::operator== (const MPEValue& other) const noexcept | |||||
{ | |||||
return normalisedValue == other.normalisedValue; | |||||
} | |||||
bool MPEValue::operator!= (const MPEValue& other) const noexcept | |||||
{ | |||||
return ! operator== (other); | |||||
} | |||||
//============================================================================== | |||||
//============================================================================== | |||||
#if JUCE_UNIT_TESTS | |||||
class MPEValueTests : public UnitTest | |||||
{ | |||||
public: | |||||
MPEValueTests() : UnitTest ("MPEValue class", "MIDI/MPE") {} | |||||
void runTest() override | |||||
{ | |||||
beginTest ("comparison operator"); | |||||
{ | |||||
MPEValue value1 = MPEValue::from7BitInt (7); | |||||
MPEValue value2 = MPEValue::from7BitInt (7); | |||||
MPEValue value3 = MPEValue::from7BitInt (8); | |||||
expect (value1 == value1); | |||||
expect (value1 == value2); | |||||
expect (value1 != value3); | |||||
} | |||||
beginTest ("special values"); | |||||
{ | |||||
expectEquals (MPEValue::minValue().as7BitInt(), 0); | |||||
expectEquals (MPEValue::minValue().as14BitInt(), 0); | |||||
expectEquals (MPEValue::centreValue().as7BitInt(), 64); | |||||
expectEquals (MPEValue::centreValue().as14BitInt(), 8192); | |||||
expectEquals (MPEValue::maxValue().as7BitInt(), 127); | |||||
expectEquals (MPEValue::maxValue().as14BitInt(), 16383); | |||||
} | |||||
beginTest ("zero/minimum value"); | |||||
{ | |||||
expectValuesConsistent (MPEValue::from7BitInt (0), 0, 0, -1.0f, 0.0f); | |||||
expectValuesConsistent (MPEValue::from14BitInt (0), 0, 0, -1.0f, 0.0f); | |||||
} | |||||
beginTest ("maximum value"); | |||||
{ | |||||
expectValuesConsistent (MPEValue::from7BitInt (127), 127, 16383, 1.0f, 1.0f); | |||||
expectValuesConsistent (MPEValue::from14BitInt (16383), 127, 16383, 1.0f, 1.0f); | |||||
} | |||||
beginTest ("centre value"); | |||||
{ | |||||
expectValuesConsistent (MPEValue::from7BitInt (64), 64, 8192, 0.0f, 0.5f); | |||||
expectValuesConsistent (MPEValue::from14BitInt (8192), 64, 8192, 0.0f, 0.5f); | |||||
} | |||||
beginTest ("value halfway between min and centre"); | |||||
{ | |||||
expectValuesConsistent (MPEValue::from7BitInt (32), 32, 4096, -0.5f, 0.25f); | |||||
expectValuesConsistent (MPEValue::from14BitInt (4096), 32, 4096, -0.5f, 0.25f); | |||||
} | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
void expectValuesConsistent (MPEValue value, | |||||
int expectedValueAs7BitInt, | |||||
int expectedValueAs14BitInt, | |||||
float expectedValueAsSignedFloat, | |||||
float expectedValueAsUnsignedFloat) | |||||
{ | |||||
expectEquals (value.as7BitInt(), expectedValueAs7BitInt); | |||||
expectEquals (value.as14BitInt(), expectedValueAs14BitInt); | |||||
expectFloatWithinRelativeError (value.asSignedFloat(), expectedValueAsSignedFloat, 0.0001f); | |||||
expectFloatWithinRelativeError (value.asUnsignedFloat(), expectedValueAsUnsignedFloat, 0.0001f); | |||||
} | |||||
//============================================================================== | |||||
void expectFloatWithinRelativeError (float actualValue, float expectedValue, float maxRelativeError) | |||||
{ | |||||
const float maxAbsoluteError = jmax (1.0f, std::fabs (expectedValue)) * maxRelativeError; | |||||
expect (std::fabs (expectedValue - actualValue) < maxAbsoluteError); | |||||
} | |||||
}; | |||||
static MPEValueTests MPEValueUnitTests; | |||||
#endif // JUCE_UNIT_TESTS | |||||
} // namespace juce |
@@ -0,0 +1,92 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
This class represents a single value for any of the MPE | |||||
dimensions of control. It supports values with 7-bit or 14-bit resolutions | |||||
(corresponding to 1 or 2 MIDI bytes, respectively). It also offers helper | |||||
functions to query the value in a variety of representations that can be | |||||
useful in an audio or MIDI context. | |||||
*/ | |||||
class JUCE_API MPEValue | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Default constructor. Constructs an MPEValue corresponding | |||||
to the centre value. | |||||
*/ | |||||
MPEValue() noexcept; | |||||
/** Constructs an MPEValue from an integer between 0 and 127 | |||||
(using 7-bit precision). | |||||
*/ | |||||
static MPEValue from7BitInt (int value) noexcept; | |||||
/** Constructs an MPEValue from an integer between 0 and 16383 | |||||
(using 14-bit precision). | |||||
*/ | |||||
static MPEValue from14BitInt (int value) noexcept; | |||||
/** Constructs an MPEValue corresponding to the centre value. */ | |||||
static MPEValue centreValue() noexcept; | |||||
/** Constructs an MPEValue corresponding to the minimum value. */ | |||||
static MPEValue minValue() noexcept; | |||||
/** Constructs an MPEValue corresponding to the maximum value. */ | |||||
static MPEValue maxValue() noexcept; | |||||
/** Retrieves the current value as an integer between 0 and 127. | |||||
Information will be lost if the value was initialised with a precision | |||||
higher than 7-bit. | |||||
*/ | |||||
int as7BitInt() const noexcept; | |||||
/** Retrieves the current value as an integer between 0 and 16383. | |||||
Resolution will be lost if the value was initialised with a precision | |||||
higher than 14-bit. | |||||
*/ | |||||
int as14BitInt() const noexcept; | |||||
/** Retrieves the current value mapped to a float between -1.0f and 1.0f. */ | |||||
float asSignedFloat() const noexcept; | |||||
/** Retrieves the current value mapped to a float between 0.0f and 1.0f. */ | |||||
float asUnsignedFloat() const noexcept; | |||||
/** Returns true if two values are equal. */ | |||||
bool operator== (const MPEValue& other) const noexcept; | |||||
/** Returns true if two values are not equal. */ | |||||
bool operator!= (const MPEValue& other) const noexcept; | |||||
private: | |||||
//============================================================================== | |||||
MPEValue (int normalisedValue); | |||||
int normalisedValue; | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,319 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
namespace | |||||
{ | |||||
void checkAndLimitZoneParameters (int minValue, | |||||
int maxValue, | |||||
int& valueToCheckAndLimit) noexcept | |||||
{ | |||||
if (valueToCheckAndLimit < minValue || valueToCheckAndLimit > maxValue) | |||||
{ | |||||
// if you hit this, one of the parameters you supplied for MPEZone | |||||
// was not within the allowed range! | |||||
// we fit this back into the allowed range here to maintain a valid | |||||
// state for the zone, but probably the resulting zone is not what you | |||||
//wanted it to be! | |||||
jassertfalse; | |||||
valueToCheckAndLimit = jlimit (minValue, maxValue, valueToCheckAndLimit); | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
MPEZone::MPEZone (int masterChannel_, | |||||
int numNoteChannels_, | |||||
int perNotePitchbendRange_, | |||||
int masterPitchbendRange_) noexcept | |||||
: masterChannel (masterChannel_), | |||||
numNoteChannels (numNoteChannels_), | |||||
perNotePitchbendRange (perNotePitchbendRange_), | |||||
masterPitchbendRange (masterPitchbendRange_) | |||||
{ | |||||
checkAndLimitZoneParameters (1, 15, masterChannel); | |||||
checkAndLimitZoneParameters (1, 16 - masterChannel, numNoteChannels); | |||||
checkAndLimitZoneParameters (0, 96, perNotePitchbendRange); | |||||
checkAndLimitZoneParameters (0, 96, masterPitchbendRange); | |||||
} | |||||
//============================================================================== | |||||
int MPEZone::getMasterChannel() const noexcept | |||||
{ | |||||
return masterChannel; | |||||
} | |||||
int MPEZone::getNumNoteChannels() const noexcept | |||||
{ | |||||
return numNoteChannels; | |||||
} | |||||
int MPEZone::getFirstNoteChannel() const noexcept | |||||
{ | |||||
return masterChannel + 1; | |||||
} | |||||
int MPEZone::getLastNoteChannel() const noexcept | |||||
{ | |||||
return masterChannel + numNoteChannels; | |||||
} | |||||
Range<int> MPEZone::getNoteChannelRange() const noexcept | |||||
{ | |||||
return Range<int>::withStartAndLength (getFirstNoteChannel(), getNumNoteChannels()); | |||||
} | |||||
bool MPEZone::isUsingChannel (int channel) const noexcept | |||||
{ | |||||
jassert (channel > 0 && channel <= 16); | |||||
return channel >= masterChannel && channel <= masterChannel + numNoteChannels; | |||||
} | |||||
bool MPEZone::isUsingChannelAsNoteChannel (int channel) const noexcept | |||||
{ | |||||
jassert (channel > 0 && channel <= 16); | |||||
return channel > masterChannel && channel <= masterChannel + numNoteChannels; | |||||
} | |||||
int MPEZone::getPerNotePitchbendRange() const noexcept | |||||
{ | |||||
return perNotePitchbendRange; | |||||
} | |||||
int MPEZone::getMasterPitchbendRange() const noexcept | |||||
{ | |||||
return masterPitchbendRange; | |||||
} | |||||
void MPEZone::setPerNotePitchbendRange (int rangeInSemitones) noexcept | |||||
{ | |||||
checkAndLimitZoneParameters (0, 96, rangeInSemitones); | |||||
perNotePitchbendRange = rangeInSemitones; | |||||
} | |||||
void MPEZone::setMasterPitchbendRange (int rangeInSemitones) noexcept | |||||
{ | |||||
checkAndLimitZoneParameters (0, 96, rangeInSemitones); | |||||
masterPitchbendRange = rangeInSemitones; | |||||
} | |||||
//============================================================================== | |||||
bool MPEZone::overlapsWith (MPEZone other) const noexcept | |||||
{ | |||||
if (masterChannel == other.masterChannel) | |||||
return true; | |||||
if (masterChannel > other.masterChannel) | |||||
return other.overlapsWith (*this); | |||||
return masterChannel + numNoteChannels >= other.masterChannel; | |||||
} | |||||
//============================================================================== | |||||
bool MPEZone::truncateToFit (MPEZone other) noexcept | |||||
{ | |||||
const int masterChannelDiff = other.masterChannel - masterChannel; | |||||
// we need at least 2 channels to be left after truncation: | |||||
// 1 master channel and 1 note channel. otherwise we can't truncate. | |||||
if (masterChannelDiff < 2) | |||||
return false; | |||||
numNoteChannels = jmin (numNoteChannels, masterChannelDiff - 1); | |||||
return true; | |||||
} | |||||
//============================================================================== | |||||
bool MPEZone::operator== (const MPEZone& other) const noexcept | |||||
{ | |||||
return masterChannel == other.masterChannel | |||||
&& numNoteChannels == other.numNoteChannels | |||||
&& perNotePitchbendRange == other.perNotePitchbendRange | |||||
&& masterPitchbendRange == other.masterPitchbendRange; | |||||
} | |||||
bool MPEZone::operator!= (const MPEZone& other) const noexcept | |||||
{ | |||||
return ! operator== (other); | |||||
} | |||||
//============================================================================== | |||||
//============================================================================== | |||||
#if JUCE_UNIT_TESTS | |||||
class MPEZoneTests : public UnitTest | |||||
{ | |||||
public: | |||||
MPEZoneTests() : UnitTest ("MPEZone class", "MIDI/MPE") {} | |||||
void runTest() override | |||||
{ | |||||
beginTest ("initialisation"); | |||||
{ | |||||
{ | |||||
MPEZone zone (1, 10); | |||||
expectEquals (zone.getMasterChannel(), 1); | |||||
expectEquals (zone.getNumNoteChannels(), 10); | |||||
expectEquals (zone.getFirstNoteChannel(), 2); | |||||
expectEquals (zone.getLastNoteChannel(), 11); | |||||
expectEquals (zone.getPerNotePitchbendRange(), 48); | |||||
expectEquals (zone.getMasterPitchbendRange(), 2); | |||||
expect (zone.isUsingChannel (1)); | |||||
expect (zone.isUsingChannel (2)); | |||||
expect (zone.isUsingChannel (10)); | |||||
expect (zone.isUsingChannel (11)); | |||||
expect (! zone.isUsingChannel (12)); | |||||
expect (! zone.isUsingChannel (16)); | |||||
expect (! zone.isUsingChannelAsNoteChannel (1)); | |||||
expect (zone.isUsingChannelAsNoteChannel (2)); | |||||
expect (zone.isUsingChannelAsNoteChannel (10)); | |||||
expect (zone.isUsingChannelAsNoteChannel (11)); | |||||
expect (! zone.isUsingChannelAsNoteChannel (12)); | |||||
expect (! zone.isUsingChannelAsNoteChannel (16)); | |||||
} | |||||
{ | |||||
MPEZone zone (5, 4); | |||||
expectEquals (zone.getMasterChannel(), 5); | |||||
expectEquals (zone.getNumNoteChannels(), 4); | |||||
expectEquals (zone.getFirstNoteChannel(), 6); | |||||
expectEquals (zone.getLastNoteChannel(), 9); | |||||
expectEquals (zone.getPerNotePitchbendRange(), 48); | |||||
expectEquals (zone.getMasterPitchbendRange(), 2); | |||||
expect (! zone.isUsingChannel (1)); | |||||
expect (! zone.isUsingChannel (4)); | |||||
expect (zone.isUsingChannel (5)); | |||||
expect (zone.isUsingChannel (6)); | |||||
expect (zone.isUsingChannel (8)); | |||||
expect (zone.isUsingChannel (9)); | |||||
expect (! zone.isUsingChannel (10)); | |||||
expect (! zone.isUsingChannel (16)); | |||||
expect (! zone.isUsingChannelAsNoteChannel (5)); | |||||
expect (zone.isUsingChannelAsNoteChannel (6)); | |||||
expect (zone.isUsingChannelAsNoteChannel (8)); | |||||
expect (zone.isUsingChannelAsNoteChannel (9)); | |||||
expect (! zone.isUsingChannelAsNoteChannel (10)); | |||||
} | |||||
} | |||||
beginTest ("getNoteChannelRange"); | |||||
{ | |||||
MPEZone zone (2, 10); | |||||
Range<int> noteChannelRange = zone.getNoteChannelRange(); | |||||
expectEquals (noteChannelRange.getStart(), 3); | |||||
expectEquals (noteChannelRange.getEnd(), 13); | |||||
} | |||||
beginTest ("setting master pitchbend range"); | |||||
{ | |||||
MPEZone zone (1, 10); | |||||
zone.setMasterPitchbendRange (96); | |||||
expectEquals (zone.getMasterPitchbendRange(), 96); | |||||
zone.setMasterPitchbendRange (0); | |||||
expectEquals (zone.getMasterPitchbendRange(), 0); | |||||
expectEquals (zone.getPerNotePitchbendRange(), 48); | |||||
} | |||||
beginTest ("setting per-note pitchbend range"); | |||||
{ | |||||
MPEZone zone (1, 10); | |||||
zone.setPerNotePitchbendRange (96); | |||||
expectEquals (zone.getPerNotePitchbendRange(), 96); | |||||
zone.setPerNotePitchbendRange (0); | |||||
expectEquals (zone.getPerNotePitchbendRange(), 0); | |||||
expectEquals (zone.getMasterPitchbendRange(), 2); | |||||
} | |||||
beginTest ("checking overlap"); | |||||
{ | |||||
testOverlapsWith (1, 10, 1, 10, true); | |||||
testOverlapsWith (1, 4, 6, 3, false); | |||||
testOverlapsWith (1, 4, 8, 3, false); | |||||
testOverlapsWith (2, 10, 2, 8, true); | |||||
testOverlapsWith (1, 10, 3, 2, true); | |||||
testOverlapsWith (3, 10, 5, 9, true); | |||||
} | |||||
beginTest ("truncating"); | |||||
{ | |||||
testTruncateToFit (1, 10, 3, 10, true, 1, 1); | |||||
testTruncateToFit (3, 10, 1, 10, false, 3, 10); | |||||
testTruncateToFit (1, 10, 5, 8, true, 1, 3); | |||||
testTruncateToFit (5, 8, 1, 10, false, 5, 8); | |||||
testTruncateToFit (1, 10, 4, 3, true, 1, 2); | |||||
testTruncateToFit (4, 3, 1, 10, false, 4, 3); | |||||
testTruncateToFit (1, 3, 5, 3, true, 1, 3); | |||||
testTruncateToFit (5, 3, 1, 3, false, 5, 3); | |||||
testTruncateToFit (1, 3, 7, 3, true, 1, 3); | |||||
testTruncateToFit (7, 3, 1, 3, false, 7, 3); | |||||
testTruncateToFit (1, 10, 2, 10, false, 1, 10); | |||||
testTruncateToFit (2, 10, 1, 10, false, 2, 10); | |||||
} | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
void testOverlapsWith (int masterChannelFirst, int numNoteChannelsFirst, | |||||
int masterChannelSecond, int numNoteChannelsSecond, | |||||
bool expectedRetVal) | |||||
{ | |||||
MPEZone first (masterChannelFirst, numNoteChannelsFirst); | |||||
MPEZone second (masterChannelSecond, numNoteChannelsSecond); | |||||
expect (first.overlapsWith (second) == expectedRetVal); | |||||
expect (second.overlapsWith (first) == expectedRetVal); | |||||
} | |||||
//============================================================================== | |||||
void testTruncateToFit (int masterChannelFirst, int numNoteChannelsFirst, | |||||
int masterChannelSecond, int numNoteChannelsSecond, | |||||
bool expectedRetVal, | |||||
int masterChannelFirstAfter, int numNoteChannelsFirstAfter) | |||||
{ | |||||
MPEZone first (masterChannelFirst, numNoteChannelsFirst); | |||||
MPEZone second (masterChannelSecond, numNoteChannelsSecond); | |||||
expect (first.truncateToFit (second) == expectedRetVal); | |||||
expectEquals (first.getMasterChannel(), masterChannelFirstAfter); | |||||
expectEquals (first.getNumNoteChannels(), numNoteChannelsFirstAfter); | |||||
} | |||||
}; | |||||
static MPEZoneTests MPEZoneUnitTests; | |||||
#endif // JUCE_UNIT_TESTS | |||||
} // namespace juce |
@@ -0,0 +1,142 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
This struct represents an MPE Zone. | |||||
An MPE Zone occupies one master MIDI channel and an arbitrary | |||||
number of note channels that immediately follow the master channel. | |||||
It also defines a pitchbend range (in semitones) to be applied for per-note | |||||
pitchbends and master pitchbends, respectively. | |||||
@see MPEZoneLayout | |||||
*/ | |||||
struct JUCE_API MPEZone | |||||
{ | |||||
/** Constructor. | |||||
Creates an MPE zone with the given master channel and | |||||
number of note channels. | |||||
@param masterChannel The master MIDI channel of the new zone. | |||||
All master (not per-note) messages should be send to this channel. | |||||
Must be between 1 and 15. Otherwise, the behaviour | |||||
is undefined. | |||||
@param numNoteChannels The number of note channels that the new zone | |||||
should use. The first note channel will be one higher | |||||
than the master channel. The number of note channels | |||||
must be at least 1 and no greater than 16 - masterChannel. | |||||
Otherwise, the behaviour is undefined. | |||||
@param perNotePitchbendRange The per-note pitchbend range in semitones of the new zone. | |||||
Must be between 0 and 96. Otherwise the behaviour is undefined. | |||||
If unspecified, the default setting of +/- 48 semitones | |||||
will be used. | |||||
@param masterPitchbendRange The master pitchbend range in semitones of the new zone. | |||||
Must be between 0 and 96. Otherwise the behaviour is undefined. | |||||
If unspecified, the default setting of +/- 2 semitones | |||||
will be used. | |||||
*/ | |||||
MPEZone (int masterChannel, | |||||
int numNoteChannels, | |||||
int perNotePitchbendRange = 48, | |||||
int masterPitchbendRange = 2) noexcept; | |||||
/* Returns the MIDI master channel number (in the range 1-16) of this zone. */ | |||||
int getMasterChannel() const noexcept; | |||||
/** Returns the number of note channels occupied by this zone. */ | |||||
int getNumNoteChannels() const noexcept; | |||||
/* Returns the MIDI channel number (in the range 1-16) of the | |||||
lowest-numbered note channel of this zone. | |||||
*/ | |||||
int getFirstNoteChannel() const noexcept; | |||||
/* Returns the MIDI channel number (in the range 1-16) of the | |||||
highest-numbered note channel of this zone. | |||||
*/ | |||||
int getLastNoteChannel() const noexcept; | |||||
/** Returns the MIDI channel numbers (in the range 1-16) of the | |||||
note channels of this zone as a Range. | |||||
*/ | |||||
Range<int> getNoteChannelRange() const noexcept; | |||||
/** Returns true if the MIDI channel (in the range 1-16) is used by this zone | |||||
either as a note channel or as the master channel; false otherwise. | |||||
*/ | |||||
bool isUsingChannel (int channel) const noexcept; | |||||
/** Returns true if the MIDI channel (in the range 1-16) is used by this zone | |||||
as a note channel; false otherwise. | |||||
*/ | |||||
bool isUsingChannelAsNoteChannel (int channel) const noexcept; | |||||
/** Returns the per-note pitchbend range in semitones set for this zone. */ | |||||
int getPerNotePitchbendRange() const noexcept; | |||||
/** Returns the master pitchbend range in semitones set for this zone. */ | |||||
int getMasterPitchbendRange() const noexcept; | |||||
/** Sets the per-note pitchbend range in semitones for this zone. */ | |||||
void setPerNotePitchbendRange (int rangeInSemitones) noexcept; | |||||
/** Sets the master pitchbend range in semitones for this zone. */ | |||||
void setMasterPitchbendRange (int rangeInSemitones) noexcept; | |||||
/** Returns true if the MIDI channels occupied by this zone | |||||
overlap with those occupied by the other zone. | |||||
*/ | |||||
bool overlapsWith (MPEZone other) const noexcept; | |||||
/** Tries to truncate this zone in such a way that the range of MIDI channels | |||||
it occupies do not overlap with the other zone, by reducing this zone's | |||||
number of note channels. | |||||
@returns true if the truncation succeeded or if no truncation is necessary | |||||
because the zones do not overlap. False if the zone cannot be truncated | |||||
in a way that would remove the overlap (in this case you need to delete | |||||
the zone to remove the overlap). | |||||
*/ | |||||
bool truncateToFit (MPEZone zoneToAvoid) noexcept; | |||||
/** @returns true if this zone is equal to the one passed in. */ | |||||
bool operator== (const MPEZone& other) const noexcept; | |||||
/** @returns true if this zone is not equal to the one passed in. */ | |||||
bool operator!= (const MPEZone& other) const noexcept; | |||||
private: | |||||
//============================================================================== | |||||
int masterChannel; | |||||
int numNoteChannels; | |||||
int perNotePitchbendRange; | |||||
int masterPitchbendRange; | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,385 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
MPEZoneLayout::MPEZoneLayout() noexcept | |||||
{ | |||||
} | |||||
MPEZoneLayout::MPEZoneLayout (const MPEZoneLayout& other) | |||||
: zones (other.zones) | |||||
{ | |||||
} | |||||
MPEZoneLayout& MPEZoneLayout::operator= (const MPEZoneLayout& other) | |||||
{ | |||||
zones = other.zones; | |||||
listeners.call (&MPEZoneLayout::Listener::zoneLayoutChanged, *this); | |||||
return *this; | |||||
} | |||||
//============================================================================== | |||||
bool MPEZoneLayout::addZone (MPEZone newZone) | |||||
{ | |||||
bool noOtherZonesModified = true; | |||||
for (int i = zones.size(); --i >= 0;) | |||||
{ | |||||
MPEZone& zone = zones.getReference (i); | |||||
if (zone.overlapsWith (newZone)) | |||||
{ | |||||
if (! zone.truncateToFit (newZone)) | |||||
zones.removeRange (i, 1); | |||||
// can't use zones.remove (i) because that requires a default c'tor :-( | |||||
noOtherZonesModified = false; | |||||
} | |||||
} | |||||
zones.add (newZone); | |||||
listeners.call (&MPEZoneLayout::Listener::zoneLayoutChanged, *this); | |||||
return noOtherZonesModified; | |||||
} | |||||
//============================================================================== | |||||
int MPEZoneLayout::getNumZones() const noexcept | |||||
{ | |||||
return zones.size(); | |||||
} | |||||
MPEZone* MPEZoneLayout::getZoneByIndex (int index) const noexcept | |||||
{ | |||||
if (zones.size() < index) | |||||
return nullptr; | |||||
return &(zones.getReference (index)); | |||||
} | |||||
void MPEZoneLayout::clearAllZones() | |||||
{ | |||||
zones.clear(); | |||||
listeners.call (&MPEZoneLayout::Listener::zoneLayoutChanged, *this); | |||||
} | |||||
//============================================================================== | |||||
void MPEZoneLayout::processNextMidiEvent (const MidiMessage& message) | |||||
{ | |||||
if (! message.isController()) | |||||
return; | |||||
MidiRPNMessage rpn; | |||||
if (rpnDetector.parseControllerMessage (message.getChannel(), | |||||
message.getControllerNumber(), | |||||
message.getControllerValue(), | |||||
rpn)) | |||||
{ | |||||
processRpnMessage (rpn); | |||||
} | |||||
} | |||||
void MPEZoneLayout::processRpnMessage (MidiRPNMessage rpn) | |||||
{ | |||||
if (rpn.parameterNumber == MPEMessages::zoneLayoutMessagesRpnNumber) | |||||
processZoneLayoutRpnMessage (rpn); | |||||
else if (rpn.parameterNumber == 0) | |||||
processPitchbendRangeRpnMessage (rpn); | |||||
} | |||||
void MPEZoneLayout::processZoneLayoutRpnMessage (MidiRPNMessage rpn) | |||||
{ | |||||
if (rpn.value < 16) | |||||
addZone (MPEZone (rpn.channel - 1, rpn.value)); | |||||
else | |||||
clearAllZones(); | |||||
} | |||||
//============================================================================== | |||||
void MPEZoneLayout::processPitchbendRangeRpnMessage (MidiRPNMessage rpn) | |||||
{ | |||||
if (MPEZone* zone = getZoneByFirstNoteChannel (rpn.channel)) | |||||
{ | |||||
if (zone->getPerNotePitchbendRange() != rpn.value) | |||||
{ | |||||
zone->setPerNotePitchbendRange (rpn.value); | |||||
listeners.call (&MPEZoneLayout::Listener::zoneLayoutChanged, *this); | |||||
return; | |||||
} | |||||
} | |||||
if (MPEZone* zone = getZoneByMasterChannel (rpn.channel)) | |||||
{ | |||||
if (zone->getMasterPitchbendRange() != rpn.value) | |||||
{ | |||||
zone->setMasterPitchbendRange (rpn.value); | |||||
listeners.call (&MPEZoneLayout::Listener::zoneLayoutChanged, *this); | |||||
return; | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void MPEZoneLayout::processNextMidiBuffer (const MidiBuffer& buffer) | |||||
{ | |||||
MidiBuffer::Iterator iter (buffer); | |||||
MidiMessage message; | |||||
int samplePosition; // not actually used, so no need to initialise. | |||||
while (iter.getNextEvent (message, samplePosition)) | |||||
processNextMidiEvent (message); | |||||
} | |||||
//============================================================================== | |||||
MPEZone* MPEZoneLayout::getZoneByChannel (int channel) const noexcept | |||||
{ | |||||
for (MPEZone* zone = zones.begin(); zone != zones.end(); ++zone) | |||||
if (zone->isUsingChannel (channel)) | |||||
return zone; | |||||
return nullptr; | |||||
} | |||||
MPEZone* MPEZoneLayout::getZoneByMasterChannel (int channel) const noexcept | |||||
{ | |||||
for (MPEZone* zone = zones.begin(); zone != zones.end(); ++zone) | |||||
if (zone->getMasterChannel() == channel) | |||||
return zone; | |||||
return nullptr; | |||||
} | |||||
MPEZone* MPEZoneLayout::getZoneByFirstNoteChannel (int channel) const noexcept | |||||
{ | |||||
for (MPEZone* zone = zones.begin(); zone != zones.end(); ++zone) | |||||
if (zone->getFirstNoteChannel() == channel) | |||||
return zone; | |||||
return nullptr; | |||||
} | |||||
MPEZone* MPEZoneLayout::getZoneByNoteChannel (int channel) const noexcept | |||||
{ | |||||
for (MPEZone* zone = zones.begin(); zone != zones.end(); ++zone) | |||||
if (zone->isUsingChannelAsNoteChannel (channel)) | |||||
return zone; | |||||
return nullptr; | |||||
} | |||||
//============================================================================== | |||||
void MPEZoneLayout::addListener (Listener* const listenerToAdd) noexcept | |||||
{ | |||||
listeners.add (listenerToAdd); | |||||
} | |||||
void MPEZoneLayout::removeListener (Listener* const listenerToRemove) noexcept | |||||
{ | |||||
listeners.remove (listenerToRemove); | |||||
} | |||||
//============================================================================== | |||||
//============================================================================== | |||||
#if JUCE_UNIT_TESTS | |||||
class MPEZoneLayoutTests : public UnitTest | |||||
{ | |||||
public: | |||||
MPEZoneLayoutTests() : UnitTest ("MPEZoneLayout class", "MIDI/MPE") {} | |||||
void runTest() override | |||||
{ | |||||
beginTest ("initialisation"); | |||||
{ | |||||
MPEZoneLayout layout; | |||||
expectEquals (layout.getNumZones(), 0); | |||||
} | |||||
beginTest ("adding zones"); | |||||
{ | |||||
MPEZoneLayout layout; | |||||
expect (layout.addZone (MPEZone (1, 7))); | |||||
expectEquals (layout.getNumZones(), 1); | |||||
expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); | |||||
expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 7); | |||||
expect (layout.addZone (MPEZone (9, 7))); | |||||
expectEquals (layout.getNumZones(), 2); | |||||
expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); | |||||
expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 7); | |||||
expectEquals (layout.getZoneByIndex (1)->getMasterChannel(), 9); | |||||
expectEquals (layout.getZoneByIndex (1)->getNumNoteChannels(), 7); | |||||
expect (! layout.addZone (MPEZone (5, 3))); | |||||
expectEquals (layout.getNumZones(), 3); | |||||
expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); | |||||
expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 3); | |||||
expectEquals (layout.getZoneByIndex (1)->getMasterChannel(), 9); | |||||
expectEquals (layout.getZoneByIndex (1)->getNumNoteChannels(), 7); | |||||
expectEquals (layout.getZoneByIndex (2)->getMasterChannel(), 5); | |||||
expectEquals (layout.getZoneByIndex (2)->getNumNoteChannels(), 3); | |||||
expect (! layout.addZone (MPEZone (5, 4))); | |||||
expectEquals (layout.getNumZones(), 2); | |||||
expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); | |||||
expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 3); | |||||
expectEquals (layout.getZoneByIndex (1)->getMasterChannel(), 5); | |||||
expectEquals (layout.getZoneByIndex (1)->getNumNoteChannels(), 4); | |||||
expect (! layout.addZone (MPEZone (6, 4))); | |||||
expectEquals (layout.getNumZones(), 2); | |||||
expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); | |||||
expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 3); | |||||
expectEquals (layout.getZoneByIndex (1)->getMasterChannel(), 6); | |||||
expectEquals (layout.getZoneByIndex (1)->getNumNoteChannels(), 4); | |||||
} | |||||
beginTest ("querying zones"); | |||||
{ | |||||
MPEZoneLayout layout; | |||||
layout.addZone (MPEZone (2, 5)); | |||||
layout.addZone (MPEZone (9, 4)); | |||||
expect (layout.getZoneByMasterChannel (1) == nullptr); | |||||
expect (layout.getZoneByMasterChannel (2) != nullptr); | |||||
expect (layout.getZoneByMasterChannel (3) == nullptr); | |||||
expect (layout.getZoneByMasterChannel (8) == nullptr); | |||||
expect (layout.getZoneByMasterChannel (9) != nullptr); | |||||
expect (layout.getZoneByMasterChannel (10) == nullptr); | |||||
expectEquals (layout.getZoneByMasterChannel (2)->getNumNoteChannels(), 5); | |||||
expectEquals (layout.getZoneByMasterChannel (9)->getNumNoteChannels(), 4); | |||||
expect (layout.getZoneByFirstNoteChannel (2) == nullptr); | |||||
expect (layout.getZoneByFirstNoteChannel (3) != nullptr); | |||||
expect (layout.getZoneByFirstNoteChannel (4) == nullptr); | |||||
expect (layout.getZoneByFirstNoteChannel (9) == nullptr); | |||||
expect (layout.getZoneByFirstNoteChannel (10) != nullptr); | |||||
expect (layout.getZoneByFirstNoteChannel (11) == nullptr); | |||||
expectEquals (layout.getZoneByFirstNoteChannel (3)->getNumNoteChannels(), 5); | |||||
expectEquals (layout.getZoneByFirstNoteChannel (10)->getNumNoteChannels(), 4); | |||||
expect (layout.getZoneByNoteChannel (2) == nullptr); | |||||
expect (layout.getZoneByNoteChannel (3) != nullptr); | |||||
expect (layout.getZoneByNoteChannel (4) != nullptr); | |||||
expect (layout.getZoneByNoteChannel (6) != nullptr); | |||||
expect (layout.getZoneByNoteChannel (7) != nullptr); | |||||
expect (layout.getZoneByNoteChannel (8) == nullptr); | |||||
expect (layout.getZoneByNoteChannel (9) == nullptr); | |||||
expect (layout.getZoneByNoteChannel (10) != nullptr); | |||||
expect (layout.getZoneByNoteChannel (11) != nullptr); | |||||
expect (layout.getZoneByNoteChannel (12) != nullptr); | |||||
expect (layout.getZoneByNoteChannel (13) != nullptr); | |||||
expect (layout.getZoneByNoteChannel (14) == nullptr); | |||||
expectEquals (layout.getZoneByNoteChannel (5)->getNumNoteChannels(), 5); | |||||
expectEquals (layout.getZoneByNoteChannel (13)->getNumNoteChannels(), 4); | |||||
} | |||||
beginTest ("clear all zones"); | |||||
{ | |||||
MPEZoneLayout layout; | |||||
expect (layout.addZone (MPEZone (1, 7))); | |||||
expect (layout.addZone (MPEZone (10, 2))); | |||||
layout.clearAllZones(); | |||||
expectEquals (layout.getNumZones(), 0); | |||||
} | |||||
beginTest ("process MIDI buffers"); | |||||
{ | |||||
MPEZoneLayout layout; | |||||
MidiBuffer buffer; | |||||
buffer = MPEMessages::addZone (MPEZone (1, 7)); | |||||
layout.processNextMidiBuffer (buffer); | |||||
expectEquals (layout.getNumZones(), 1); | |||||
expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); | |||||
expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 7); | |||||
buffer = MPEMessages::addZone (MPEZone (9, 7)); | |||||
layout.processNextMidiBuffer (buffer); | |||||
expectEquals (layout.getNumZones(), 2); | |||||
expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); | |||||
expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 7); | |||||
expectEquals (layout.getZoneByIndex (1)->getMasterChannel(), 9); | |||||
expectEquals (layout.getZoneByIndex (1)->getNumNoteChannels(), 7); | |||||
MPEZone zone (1, 10); | |||||
buffer = MPEMessages::addZone (zone); | |||||
layout.processNextMidiBuffer (buffer); | |||||
expectEquals (layout.getNumZones(), 1); | |||||
expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); | |||||
expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 10); | |||||
zone.setPerNotePitchbendRange (33); | |||||
zone.setMasterPitchbendRange (44); | |||||
buffer = MPEMessages::masterPitchbendRange (zone); | |||||
buffer.addEvents (MPEMessages::perNotePitchbendRange (zone), 0, -1, 0); | |||||
layout.processNextMidiBuffer (buffer); | |||||
expectEquals (layout.getZoneByIndex (0)->getPerNotePitchbendRange(), 33); | |||||
expectEquals (layout.getZoneByIndex (0)->getMasterPitchbendRange(), 44); | |||||
} | |||||
beginTest ("process individual MIDI messages"); | |||||
{ | |||||
MPEZoneLayout layout; | |||||
layout.processNextMidiEvent (MidiMessage (0x80, 0x59, 0xd0)); // unrelated note-off msg | |||||
layout.processNextMidiEvent (MidiMessage (0xb1, 0x64, 0x06)); // RPN part 1 | |||||
layout.processNextMidiEvent (MidiMessage (0xb1, 0x65, 0x00)); // RPN part 2 | |||||
layout.processNextMidiEvent (MidiMessage (0xb8, 0x0b, 0x66)); // unrelated CC msg | |||||
layout.processNextMidiEvent (MidiMessage (0xb1, 0x06, 0x03)); // RPN part 3 | |||||
layout.processNextMidiEvent (MidiMessage (0x90, 0x60, 0x00)); // unrelated note-on msg | |||||
expectEquals (layout.getNumZones(), 1); | |||||
expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); | |||||
expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 3); | |||||
} | |||||
} | |||||
}; | |||||
static MPEZoneLayoutTests MPEZoneLayoutUnitTests; | |||||
#endif // JUCE_UNIT_TESTS | |||||
} // namespace juce |
@@ -0,0 +1,161 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
This class represents the current MPE zone layout of a device | |||||
capable of handling MPE. | |||||
Use the MPEMessages helper class to convert the zone layout represented | |||||
by this object to MIDI message sequences that you can send to an Expressive | |||||
MIDI device to set its zone layout, add zones etc. | |||||
@see MPEZone, MPEInstrument | |||||
*/ | |||||
class JUCE_API MPEZoneLayout | |||||
{ | |||||
public: | |||||
/** Default constructor. | |||||
This will create a layout with no MPE zones. | |||||
You can add an MPE zone using the method addZone. | |||||
*/ | |||||
MPEZoneLayout() noexcept; | |||||
/** Copy constuctor. | |||||
This will not copy the listeners registered to the MPEZoneLayout. | |||||
*/ | |||||
MPEZoneLayout (const MPEZoneLayout& other); | |||||
/** Copy assignment operator. | |||||
This will not copy the listeners registered to the MPEZoneLayout. | |||||
*/ | |||||
MPEZoneLayout& operator= (const MPEZoneLayout& other); | |||||
/** Adds a new MPE zone to the layout. | |||||
@param newZone The zone to add. | |||||
@return true if the zone was added without modifying any other zones | |||||
added previously to the same zone layout object (if any); | |||||
false if any existing MPE zones had to be truncated | |||||
or deleted entirely in order to to add this new zone. | |||||
(Note: the zone itself will always be added with the channel bounds | |||||
that were specified; this will not fail.) | |||||
*/ | |||||
bool addZone (MPEZone newZone); | |||||
/** Removes all currently present MPE zones. */ | |||||
void clearAllZones(); | |||||
/** Pass incoming MIDI messages to an object of this class if you want the | |||||
zone layout to properly react to MPE RPN messages like an | |||||
MPE device. | |||||
MPEMessages::rpnNumber will add or remove zones; RPN 0 will | |||||
set the per-note or master pitchbend ranges. | |||||
Any other MIDI messages will be ignored by this class. | |||||
@see MPEMessages | |||||
*/ | |||||
void processNextMidiEvent (const MidiMessage& message); | |||||
/** Pass incoming MIDI buffers to an object of this class if you want the | |||||
zone layout to properly react to MPE RPN messages like an | |||||
MPE device. | |||||
MPEMessages::rpnNumber will add or remove zones; RPN 0 will | |||||
set the per-note or master pitchbend ranges. | |||||
Any other MIDI messages will be ignored by this class. | |||||
@see MPEMessages | |||||
*/ | |||||
void processNextMidiBuffer (const MidiBuffer& buffer); | |||||
/** Returns the current number of MPE zones. */ | |||||
int getNumZones() const noexcept; | |||||
/** Returns a pointer to the MPE zone at the given index, or nullptr if there | |||||
is no such zone. Zones are sorted by insertion order (most recently added | |||||
zone last). | |||||
*/ | |||||
MPEZone* getZoneByIndex (int index) const noexcept; | |||||
/** Returns a pointer to the zone which uses the specified channel (1-16), | |||||
or nullptr if there is no such zone. | |||||
*/ | |||||
MPEZone* getZoneByChannel (int midiChannel) const noexcept; | |||||
/** Returns a pointer to the zone which has the specified channel (1-16) | |||||
as its master channel, or nullptr if there is no such zone. | |||||
*/ | |||||
MPEZone* getZoneByMasterChannel (int midiChannel) const noexcept; | |||||
/** Returns a pointer to the zone which has the specified channel (1-16) | |||||
as its first note channel, or nullptr if there is no such zone. | |||||
*/ | |||||
MPEZone* getZoneByFirstNoteChannel (int midiChannel) const noexcept; | |||||
/** Returns a pointer to the zone which has the specified channel (1-16) | |||||
as one of its note channels, or nullptr if there is no such zone. | |||||
*/ | |||||
MPEZone* getZoneByNoteChannel (int midiChannel) const noexcept; | |||||
//============================================================================== | |||||
/** Listener class. Derive from this class to allow your class to be | |||||
notified about changes to the zone layout. | |||||
*/ | |||||
class Listener | |||||
{ | |||||
public: | |||||
/** Destructor. */ | |||||
virtual ~Listener() {} | |||||
/** Implement this callback to be notified about any changes to this | |||||
MPEZoneLayout. Will be called whenever a zone is added, zones are | |||||
removed, or any zone's master or note pitchbend ranges change. | |||||
*/ | |||||
virtual void zoneLayoutChanged (const MPEZoneLayout& layout) = 0; | |||||
}; | |||||
//============================================================================== | |||||
/** Adds a listener. */ | |||||
void addListener (Listener* const listenerToAdd) noexcept; | |||||
/** Removes a listener. */ | |||||
void removeListener (Listener* const listenerToRemove) noexcept; | |||||
private: | |||||
//============================================================================== | |||||
Array<MPEZone> zones; | |||||
MidiRPNDetector rpnDetector; | |||||
ListenerList<Listener> listeners; | |||||
void processRpnMessage (MidiRPNMessage); | |||||
void processZoneLayoutRpnMessage (MidiRPNMessage); | |||||
void processPitchbendRangeRpnMessage (MidiRPNMessage); | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,309 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
#if JUCE_MAC || JUCE_IOS | |||||
struct CoreAudioLayouts | |||||
{ | |||||
//============================================================================== | |||||
/** Convert CoreAudio's native AudioChannelLayout to JUCE's AudioChannelSet. | |||||
Note that this method cannot preserve the order of channels. | |||||
*/ | |||||
static AudioChannelSet fromCoreAudio (const AudioChannelLayout& layout) | |||||
{ | |||||
return AudioChannelSet::channelSetWithChannels (getCoreAudioLayoutChannels (layout)); | |||||
} | |||||
/** Convert CoreAudio's native AudioChannelLayoutTag to JUCE's AudioChannelSet. | |||||
Note that this method cannot preserve the order of channels. | |||||
*/ | |||||
static AudioChannelSet fromCoreAudio (AudioChannelLayoutTag layoutTag) | |||||
{ | |||||
return AudioChannelSet::channelSetWithChannels (getSpeakerLayoutForCoreAudioTag (layoutTag)); | |||||
} | |||||
/** Convert JUCE's AudioChannelSet to CoreAudio's AudioChannelLayoutTag. | |||||
Note that this method cannot preserve the order of channels. | |||||
*/ | |||||
static AudioChannelLayoutTag toCoreAudio (const AudioChannelSet& set) | |||||
{ | |||||
for (auto* tbl = SpeakerLayoutTable::get(); tbl->tag != 0; ++tbl) | |||||
{ | |||||
AudioChannelSet caSet; | |||||
for (int i = 0; i < numElementsInArray (tbl->channelTypes) | |||||
&& tbl->channelTypes[i] != AudioChannelSet::unknown; ++i) | |||||
caSet.addChannel (tbl->channelTypes[i]); | |||||
if (caSet == set) | |||||
return tbl->tag; | |||||
} | |||||
return kAudioChannelLayoutTag_DiscreteInOrder | static_cast<AudioChannelLayoutTag> (set.size()); | |||||
} | |||||
static const Array<AudioChannelLayoutTag>& getKnownCoreAudioTags() | |||||
{ | |||||
static Array<AudioChannelLayoutTag> tags (createKnownCoreAudioTags()); | |||||
return tags; | |||||
} | |||||
//============================================================================== | |||||
/** Convert CoreAudio's native AudioChannelLayout to an array of JUCE ChannelTypes. */ | |||||
static Array<AudioChannelSet::ChannelType> getCoreAudioLayoutChannels (const AudioChannelLayout& layout) | |||||
{ | |||||
switch (layout.mChannelLayoutTag & 0xffff0000) | |||||
{ | |||||
case kAudioChannelLayoutTag_UseChannelBitmap: | |||||
return AudioChannelSet::fromWaveChannelMask (static_cast<int> (layout.mChannelBitmap)).getChannelTypes(); | |||||
case kAudioChannelLayoutTag_UseChannelDescriptions: | |||||
{ | |||||
Array<AudioChannelSet::ChannelType> channels; | |||||
for (UInt32 i = 0; i < layout.mNumberChannelDescriptions; ++i) | |||||
channels.addIfNotAlreadyThere (getChannelTypeFromAudioChannelLabel (layout.mChannelDescriptions[i].mChannelLabel)); | |||||
// different speaker mappings may point to the same JUCE speaker so fill up | |||||
// this array with discrete channels | |||||
for (int j = 0; channels.size() < static_cast<int> (layout.mNumberChannelDescriptions); ++j) | |||||
channels.addIfNotAlreadyThere (static_cast<AudioChannelSet::ChannelType> (AudioChannelSet::discreteChannel0 + j)); | |||||
return channels; | |||||
} | |||||
case kAudioChannelLayoutTag_DiscreteInOrder: | |||||
return AudioChannelSet::discreteChannels (static_cast<int> (layout.mChannelLayoutTag) & 0xffff).getChannelTypes(); | |||||
default: | |||||
break; | |||||
} | |||||
return getSpeakerLayoutForCoreAudioTag (layout.mChannelLayoutTag); | |||||
} | |||||
static Array<AudioChannelSet::ChannelType> getSpeakerLayoutForCoreAudioTag (AudioChannelLayoutTag tag) | |||||
{ | |||||
// You need to specify the full AudioChannelLayout when using | |||||
// the UseChannelBitmap and UseChannelDescriptions layout tag | |||||
jassert (tag != kAudioChannelLayoutTag_UseChannelBitmap && tag != kAudioChannelLayoutTag_UseChannelDescriptions); | |||||
Array<AudioChannelSet::ChannelType> speakers; | |||||
for (auto* tbl = SpeakerLayoutTable::get(); tbl->tag != 0; ++tbl) | |||||
{ | |||||
if (tag == tbl->tag) | |||||
{ | |||||
for (int i = 0; i < numElementsInArray (tbl->channelTypes) | |||||
&& tbl->channelTypes[i] != AudioChannelSet::unknown; ++i) | |||||
speakers.add (tbl->channelTypes[i]); | |||||
return speakers; | |||||
} | |||||
} | |||||
auto numChannels = tag & 0xffff; | |||||
for (UInt32 i = 0; i < numChannels; ++i) | |||||
speakers.add (static_cast<AudioChannelSet::ChannelType> (AudioChannelSet::discreteChannel0 + i)); | |||||
return speakers; | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
struct LayoutTagSpeakerList | |||||
{ | |||||
AudioChannelLayoutTag tag; | |||||
AudioChannelSet::ChannelType channelTypes[16]; | |||||
}; | |||||
static Array<AudioChannelLayoutTag> createKnownCoreAudioTags() | |||||
{ | |||||
Array<AudioChannelLayoutTag> tags; | |||||
for (auto* tbl = SpeakerLayoutTable::get(); tbl->tag != 0; ++tbl) | |||||
tags.addIfNotAlreadyThere (tbl->tag); | |||||
return tags; | |||||
} | |||||
//============================================================================== | |||||
// This list has been derived from https://pastebin.com/24dQ4BPJ | |||||
// Apple channel labels have been replaced by JUCE channel names | |||||
// This means that some layouts will be identical in JUCE but not in CoreAudio | |||||
// In Apple's official definition the following tags exist with the same speaker layout and order | |||||
// even when *not* represented in JUCE channels | |||||
// kAudioChannelLayoutTag_Binaural = kAudioChannelLayoutTag_Stereo | |||||
// kAudioChannelLayoutTag_MPEG_5_0_B = kAudioChannelLayoutTag_Pentagonal | |||||
// kAudioChannelLayoutTag_ITU_2_2 = kAudioChannelLayoutTag_Quadraphonic | |||||
// kAudioChannelLayoutTag_AudioUnit_6_0 = kAudioChannelLayoutTag_Hexagonal | |||||
struct SpeakerLayoutTable : AudioChannelSet // save us some typing | |||||
{ | |||||
static LayoutTagSpeakerList* get() noexcept | |||||
{ | |||||
static LayoutTagSpeakerList tbl[] = { | |||||
// list layouts for which there is a corresponding named AudioChannelSet first | |||||
{ kAudioChannelLayoutTag_Mono, { centre } }, | |||||
{ kAudioChannelLayoutTag_Stereo, { left, right } }, | |||||
{ kAudioChannelLayoutTag_MPEG_3_0_A, { left, right, centre } }, | |||||
{ kAudioChannelLayoutTag_ITU_2_1, { left, right, centreSurround } }, | |||||
{ kAudioChannelLayoutTag_MPEG_4_0_A, { left, right, centre, centreSurround } }, | |||||
{ kAudioChannelLayoutTag_MPEG_5_0_A, { left, right, centre, leftSurround, rightSurround } }, | |||||
{ kAudioChannelLayoutTag_MPEG_5_1_A, { left, right, centre, LFE, leftSurround, rightSurround } }, | |||||
{ kAudioChannelLayoutTag_AudioUnit_6_0, { left, right, leftSurround, rightSurround, centre, centreSurround } }, | |||||
{ kAudioChannelLayoutTag_MPEG_6_1_A, { left, right, centre, LFE, leftSurround, rightSurround, centreSurround } }, | |||||
{ kAudioChannelLayoutTag_DTS_6_0_A, { leftSurroundSide, rightSurroundSide, left, right, leftSurround, rightSurround } }, | |||||
{ kAudioChannelLayoutTag_DTS_6_1_A, { leftSurroundSide, rightSurroundSide, left, right, leftSurround, rightSurround, LFE } }, | |||||
{ kAudioChannelLayoutTag_AudioUnit_7_0, { left, right, leftSurroundSide, rightSurroundSide, centre, leftSurroundRear, rightSurroundRear } }, | |||||
{ kAudioChannelLayoutTag_AudioUnit_7_0_Front, { left, right, leftSurround, rightSurround, centre, leftCentre, rightCentre } }, | |||||
{ kAudioChannelLayoutTag_MPEG_7_1_C, { left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear } }, | |||||
{ kAudioChannelLayoutTag_MPEG_7_1_A, { left, right, centre, LFE, leftSurround, rightSurround, leftCentre, rightCentre } }, | |||||
{ kAudioChannelLayoutTag_Ambisonic_B_Format, { ambisonicW, ambisonicX, ambisonicY, ambisonicZ } }, | |||||
{ kAudioChannelLayoutTag_Quadraphonic, { left, right, leftSurround, rightSurround } }, | |||||
{ kAudioChannelLayoutTag_Pentagonal, { left, right, leftSurroundRear, rightSurroundRear, centre } }, | |||||
{ kAudioChannelLayoutTag_Hexagonal, { left, right, leftSurroundRear, rightSurroundRear, centre, centreSurround } }, | |||||
{ kAudioChannelLayoutTag_Octagonal, { left, right, leftSurround, rightSurround, centre, centreSurround, wideLeft, wideRight } }, | |||||
// more uncommon layouts | |||||
{ kAudioChannelLayoutTag_StereoHeadphones, { left, right } }, | |||||
{ kAudioChannelLayoutTag_MatrixStereo, { left, right } }, | |||||
{ kAudioChannelLayoutTag_MidSide, { centre, discreteChannel0 } }, | |||||
{ kAudioChannelLayoutTag_XY, { ambisonicX, ambisonicY } }, | |||||
{ kAudioChannelLayoutTag_Binaural, { left, right } }, | |||||
{ kAudioChannelLayoutTag_Cube, { left, right, leftSurround, rightSurround, topFrontLeft, topFrontRight, topRearLeft, topRearRight } }, | |||||
{ kAudioChannelLayoutTag_MPEG_3_0_B, { centre, left, right } }, | |||||
{ kAudioChannelLayoutTag_MPEG_4_0_B, { centre, left, right, centreSurround } }, | |||||
{ kAudioChannelLayoutTag_MPEG_5_0_B, { left, right, leftSurround, rightSurround, centre } }, | |||||
{ kAudioChannelLayoutTag_MPEG_5_0_C, { left, centre, right, leftSurround, rightSurround } }, | |||||
{ kAudioChannelLayoutTag_MPEG_5_0_D, { centre, left, right, leftSurround, rightSurround } }, | |||||
{ kAudioChannelLayoutTag_MPEG_5_1_B, { left, right, leftSurround, rightSurround, centre, LFE } }, | |||||
{ kAudioChannelLayoutTag_MPEG_5_1_C, { left, centre, right, leftSurround, rightSurround, LFE } }, | |||||
{ kAudioChannelLayoutTag_MPEG_5_1_D, { centre, left, right, leftSurround, rightSurround, LFE } }, | |||||
{ kAudioChannelLayoutTag_MPEG_7_1_B, { centre, leftCentre, rightCentre, left, right, leftSurround, rightSurround, LFE } }, | |||||
{ kAudioChannelLayoutTag_Emagic_Default_7_1, { left, right, leftSurround, rightSurround, centre, LFE, leftCentre, rightCentre } }, | |||||
{ kAudioChannelLayoutTag_SMPTE_DTV, { left, right, centre, LFE, leftSurround, rightSurround, discreteChannel0 /* leftMatrixTotal */, (ChannelType) (discreteChannel0 + 1) /* rightMatrixTotal */} }, | |||||
{ kAudioChannelLayoutTag_ITU_2_2, { left, right, leftSurround, rightSurround } }, | |||||
{ kAudioChannelLayoutTag_DVD_4, { left, right, LFE } }, | |||||
{ kAudioChannelLayoutTag_DVD_5, { left, right, LFE, centreSurround } }, | |||||
{ kAudioChannelLayoutTag_DVD_6, { left, right, LFE, leftSurround, rightSurround } }, | |||||
{ kAudioChannelLayoutTag_DVD_10, { left, right, centre, LFE } }, | |||||
{ kAudioChannelLayoutTag_DVD_11, { left, right, centre, LFE, centreSurround } }, | |||||
{ kAudioChannelLayoutTag_DVD_18, { left, right, leftSurround, rightSurround, LFE } }, | |||||
{ kAudioChannelLayoutTag_AAC_6_0, { centre, left, right, leftSurround, rightSurround, centreSurround } }, | |||||
{ kAudioChannelLayoutTag_AAC_6_1, { centre, left, right, leftSurround, rightSurround, centreSurround, LFE } }, | |||||
{ kAudioChannelLayoutTag_AAC_7_0, { centre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear } }, | |||||
{ kAudioChannelLayoutTag_AAC_7_1_B, { centre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, LFE } }, | |||||
{ kAudioChannelLayoutTag_AAC_7_1_C, { centre, left, right, leftSurround, rightSurround, LFE, topFrontLeft, topFrontRight } }, | |||||
{ kAudioChannelLayoutTag_AAC_Octagonal, { centre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, centreSurround } }, | |||||
{ kAudioChannelLayoutTag_TMH_10_2_std, { left, right, centre, topFrontCentre, leftSurroundSide, rightSurroundSide, leftSurround, rightSurround, topFrontLeft, topFrontRight, wideLeft, wideRight, topRearCentre, centreSurround, LFE, LFE2 } }, | |||||
{ kAudioChannelLayoutTag_AC3_1_0_1, { centre, LFE } }, | |||||
{ kAudioChannelLayoutTag_AC3_3_0, { left, centre, right } }, | |||||
{ kAudioChannelLayoutTag_AC3_3_1, { left, centre, right, centreSurround } }, | |||||
{ kAudioChannelLayoutTag_AC3_3_0_1, { left, centre, right, LFE } }, | |||||
{ kAudioChannelLayoutTag_AC3_2_1_1, { left, right, centreSurround, LFE } }, | |||||
{ kAudioChannelLayoutTag_AC3_3_1_1, { left, centre, right, centreSurround, LFE } }, | |||||
{ kAudioChannelLayoutTag_EAC_6_0_A, { left, centre, right, leftSurround, rightSurround, centreSurround } }, | |||||
{ kAudioChannelLayoutTag_EAC_7_0_A, { left, centre, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear } }, | |||||
{ kAudioChannelLayoutTag_EAC3_6_1_A, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround } }, | |||||
{ kAudioChannelLayoutTag_EAC3_6_1_B, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround } }, | |||||
{ kAudioChannelLayoutTag_EAC3_6_1_C, { left, centre, right, leftSurround, rightSurround, LFE, topFrontCentre } }, | |||||
{ kAudioChannelLayoutTag_EAC3_7_1_A, { left, centre, right, leftSurround, rightSurround, LFE, leftSurroundRear, rightSurroundRear } }, | |||||
{ kAudioChannelLayoutTag_EAC3_7_1_B, { left, centre, right, leftSurround, rightSurround, LFE, leftCentre, rightCentre } }, | |||||
{ kAudioChannelLayoutTag_EAC3_7_1_C, { left, centre, right, leftSurround, rightSurround, LFE, leftSurroundSide, rightSurroundSide } }, | |||||
{ kAudioChannelLayoutTag_EAC3_7_1_D, { left, centre, right, leftSurround, rightSurround, LFE, wideLeft, wideRight } }, | |||||
{ kAudioChannelLayoutTag_EAC3_7_1_E, { left, centre, right, leftSurround, rightSurround, LFE, topFrontLeft, topFrontRight } }, | |||||
{ kAudioChannelLayoutTag_EAC3_7_1_F, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround, topMiddle } }, | |||||
{ kAudioChannelLayoutTag_EAC3_7_1_G, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround, topFrontCentre } }, | |||||
{ kAudioChannelLayoutTag_EAC3_7_1_H, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround, topFrontCentre } }, | |||||
{ kAudioChannelLayoutTag_DTS_3_1, { centre, left, right, LFE } }, | |||||
{ kAudioChannelLayoutTag_DTS_4_1, { centre, left, right, centreSurround, LFE } }, | |||||
{ kAudioChannelLayoutTag_DTS_6_0_B, { centre, left, right, leftSurroundRear, rightSurroundRear, centreSurround } }, | |||||
{ kAudioChannelLayoutTag_DTS_6_0_C, { centre, centreSurround, left, right, leftSurroundRear, rightSurroundRear } }, | |||||
{ kAudioChannelLayoutTag_DTS_6_1_B, { centre, left, right, leftSurroundRear, rightSurroundRear, centreSurround, LFE } }, | |||||
{ kAudioChannelLayoutTag_DTS_6_1_C, { centre, centreSurround, left, right, leftSurroundRear, rightSurroundRear, LFE } }, | |||||
{ kAudioChannelLayoutTag_DTS_6_1_D, { centre, left, right, leftSurround, rightSurround, LFE, centreSurround } }, | |||||
{ kAudioChannelLayoutTag_DTS_7_0, { leftCentre, centre, rightCentre, left, right, leftSurround, rightSurround } }, | |||||
{ kAudioChannelLayoutTag_DTS_7_1, { leftCentre, centre, rightCentre, left, right, leftSurround, rightSurround, LFE } }, | |||||
{ kAudioChannelLayoutTag_DTS_8_0_A, { leftCentre, rightCentre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear } }, | |||||
{ kAudioChannelLayoutTag_DTS_8_0_B, { leftCentre, centre, rightCentre, left, right, leftSurround, centreSurround, rightSurround } }, | |||||
{ kAudioChannelLayoutTag_DTS_8_1_A, { leftCentre, rightCentre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, LFE } }, | |||||
{ kAudioChannelLayoutTag_DTS_8_1_B, { leftCentre, centre, rightCentre, left, right, leftSurround, centreSurround, rightSurround, LFE } }, | |||||
{ 0, {} } | |||||
}; | |||||
return tbl; | |||||
} | |||||
}; | |||||
//============================================================================== | |||||
static AudioChannelSet::ChannelType getChannelTypeFromAudioChannelLabel (AudioChannelLabel label) noexcept | |||||
{ | |||||
if (label >= kAudioChannelLabel_Discrete_0 && label <= kAudioChannelLabel_Discrete_65535) | |||||
{ | |||||
const unsigned int discreteChannelNum = label - kAudioChannelLabel_Discrete_0; | |||||
return static_cast<AudioChannelSet::ChannelType> (AudioChannelSet::discreteChannel0 + discreteChannelNum); | |||||
} | |||||
switch (label) | |||||
{ | |||||
case kAudioChannelLabel_Center: | |||||
case kAudioChannelLabel_Mono: return AudioChannelSet::centre; | |||||
case kAudioChannelLabel_Left: | |||||
case kAudioChannelLabel_HeadphonesLeft: return AudioChannelSet::left; | |||||
case kAudioChannelLabel_Right: | |||||
case kAudioChannelLabel_HeadphonesRight: return AudioChannelSet::right; | |||||
case kAudioChannelLabel_LFEScreen: return AudioChannelSet::LFE; | |||||
case kAudioChannelLabel_LeftSurround: return AudioChannelSet::leftSurround; | |||||
case kAudioChannelLabel_RightSurround: return AudioChannelSet::rightSurround; | |||||
case kAudioChannelLabel_LeftCenter: return AudioChannelSet::leftCentre; | |||||
case kAudioChannelLabel_RightCenter: return AudioChannelSet::rightCentre; | |||||
case kAudioChannelLabel_CenterSurround: return AudioChannelSet::surround; | |||||
case kAudioChannelLabel_LeftSurroundDirect: return AudioChannelSet::leftSurroundSide; | |||||
case kAudioChannelLabel_RightSurroundDirect: return AudioChannelSet::rightSurroundSide; | |||||
case kAudioChannelLabel_TopCenterSurround: return AudioChannelSet::topMiddle; | |||||
case kAudioChannelLabel_VerticalHeightLeft: return AudioChannelSet::topFrontLeft; | |||||
case kAudioChannelLabel_VerticalHeightRight: return AudioChannelSet::topFrontRight; | |||||
case kAudioChannelLabel_VerticalHeightCenter: return AudioChannelSet::topFrontCentre; | |||||
case kAudioChannelLabel_TopBackLeft: return AudioChannelSet::topRearLeft; | |||||
case kAudioChannelLabel_RearSurroundLeft: return AudioChannelSet::leftSurroundRear; | |||||
case kAudioChannelLabel_TopBackRight: return AudioChannelSet::topRearRight; | |||||
case kAudioChannelLabel_RearSurroundRight: return AudioChannelSet::rightSurroundRear; | |||||
case kAudioChannelLabel_TopBackCenter: return AudioChannelSet::topRearCentre; | |||||
case kAudioChannelLabel_LFE2: return AudioChannelSet::LFE2; | |||||
case kAudioChannelLabel_LeftWide: return AudioChannelSet::wideLeft; | |||||
case kAudioChannelLabel_RightWide: return AudioChannelSet::wideRight; | |||||
case kAudioChannelLabel_Ambisonic_W: return AudioChannelSet::ambisonicW; | |||||
case kAudioChannelLabel_Ambisonic_X: return AudioChannelSet::ambisonicX; | |||||
case kAudioChannelLabel_Ambisonic_Y: return AudioChannelSet::ambisonicY; | |||||
case kAudioChannelLabel_Ambisonic_Z: return AudioChannelSet::ambisonicZ; | |||||
default: return AudioChannelSet::unknown; | |||||
} | |||||
} | |||||
}; | |||||
#endif | |||||
} // namespace juce |
@@ -0,0 +1,177 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Used by AudioSource::getNextAudioBlock(). | |||||
*/ | |||||
struct JUCE_API AudioSourceChannelInfo | |||||
{ | |||||
/** Creates an uninitialised AudioSourceChannelInfo. */ | |||||
AudioSourceChannelInfo() noexcept | |||||
{ | |||||
} | |||||
/** Creates an AudioSourceChannelInfo. */ | |||||
AudioSourceChannelInfo (AudioSampleBuffer* bufferToUse, | |||||
int startSampleOffset, int numSamplesToUse) noexcept | |||||
: buffer (bufferToUse), | |||||
startSample (startSampleOffset), | |||||
numSamples (numSamplesToUse) | |||||
{ | |||||
} | |||||
/** Creates an AudioSourceChannelInfo that uses the whole of a buffer. | |||||
Note that the buffer provided must not be deleted while the | |||||
AudioSourceChannelInfo is still using it. | |||||
*/ | |||||
explicit AudioSourceChannelInfo (AudioSampleBuffer& bufferToUse) noexcept | |||||
: buffer (&bufferToUse), | |||||
startSample (0), | |||||
numSamples (bufferToUse.getNumSamples()) | |||||
{ | |||||
} | |||||
/** The destination buffer to fill with audio data. | |||||
When the AudioSource::getNextAudioBlock() method is called, the active section | |||||
of this buffer should be filled with whatever output the source produces. | |||||
Only the samples specified by the startSample and numSamples members of this structure | |||||
should be affected by the call. | |||||
The contents of the buffer when it is passed to the AudioSource::getNextAudioBlock() | |||||
method can be treated as the input if the source is performing some kind of filter operation, | |||||
but should be cleared if this is not the case - the clearActiveBufferRegion() is | |||||
a handy way of doing this. | |||||
The number of channels in the buffer could be anything, so the AudioSource | |||||
must cope with this in whatever way is appropriate for its function. | |||||
*/ | |||||
AudioSampleBuffer* buffer; | |||||
/** The first sample in the buffer from which the callback is expected | |||||
to write data. */ | |||||
int startSample; | |||||
/** The number of samples in the buffer which the callback is expected to | |||||
fill with data. */ | |||||
int numSamples; | |||||
/** Convenient method to clear the buffer if the source is not producing any data. */ | |||||
void clearActiveBufferRegion() const | |||||
{ | |||||
if (buffer != nullptr) | |||||
buffer->clear (startSample, numSamples); | |||||
} | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
Base class for objects that can produce a continuous stream of audio. | |||||
An AudioSource has two states: 'prepared' and 'unprepared'. | |||||
When a source needs to be played, it is first put into a 'prepared' state by a call to | |||||
prepareToPlay(), and then repeated calls will be made to its getNextAudioBlock() method to | |||||
process the audio data. | |||||
Once playback has finished, the releaseResources() method is called to put the stream | |||||
back into an 'unprepared' state. | |||||
@see AudioFormatReaderSource, ResamplingAudioSource | |||||
*/ | |||||
class JUCE_API AudioSource | |||||
{ | |||||
protected: | |||||
//============================================================================== | |||||
/** Creates an AudioSource. */ | |||||
AudioSource() noexcept {} | |||||
public: | |||||
/** Destructor. */ | |||||
virtual ~AudioSource() {} | |||||
//============================================================================== | |||||
/** Tells the source to prepare for playing. | |||||
An AudioSource has two states: prepared and unprepared. | |||||
The prepareToPlay() method is guaranteed to be called at least once on an 'unpreprared' | |||||
source to put it into a 'prepared' state before any calls will be made to getNextAudioBlock(). | |||||
This callback allows the source to initialise any resources it might need when playing. | |||||
Once playback has finished, the releaseResources() method is called to put the stream | |||||
back into an 'unprepared' state. | |||||
Note that this method could be called more than once in succession without | |||||
a matching call to releaseResources(), so make sure your code is robust and | |||||
can handle that kind of situation. | |||||
@param samplesPerBlockExpected the number of samples that the source | |||||
will be expected to supply each time its | |||||
getNextAudioBlock() method is called. This | |||||
number may vary slightly, because it will be dependent | |||||
on audio hardware callbacks, and these aren't | |||||
guaranteed to always use a constant block size, so | |||||
the source should be able to cope with small variations. | |||||
@param sampleRate the sample rate that the output will be used at - this | |||||
is needed by sources such as tone generators. | |||||
@see releaseResources, getNextAudioBlock | |||||
*/ | |||||
virtual void prepareToPlay (int samplesPerBlockExpected, | |||||
double sampleRate) = 0; | |||||
/** Allows the source to release anything it no longer needs after playback has stopped. | |||||
This will be called when the source is no longer going to have its getNextAudioBlock() | |||||
method called, so it should release any spare memory, etc. that it might have | |||||
allocated during the prepareToPlay() call. | |||||
Note that there's no guarantee that prepareToPlay() will actually have been called before | |||||
releaseResources(), and it may be called more than once in succession, so make sure your | |||||
code is robust and doesn't make any assumptions about when it will be called. | |||||
@see prepareToPlay, getNextAudioBlock | |||||
*/ | |||||
virtual void releaseResources() = 0; | |||||
/** Called repeatedly to fetch subsequent blocks of audio data. | |||||
After calling the prepareToPlay() method, this callback will be made each | |||||
time the audio playback hardware (or whatever other destination the audio | |||||
data is going to) needs another block of data. | |||||
It will generally be called on a high-priority system thread, or possibly even | |||||
an interrupt, so be careful not to do too much work here, as that will cause | |||||
audio glitches! | |||||
@see AudioSourceChannelInfo, prepareToPlay, releaseResources | |||||
*/ | |||||
virtual void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) = 0; | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,314 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
BufferingAudioSource::BufferingAudioSource (PositionableAudioSource* s, | |||||
TimeSliceThread& thread, | |||||
const bool deleteSourceWhenDeleted, | |||||
const int bufferSizeSamples, | |||||
const int numChannels, | |||||
bool prefillBufferOnPrepareToPlay) | |||||
: source (s, deleteSourceWhenDeleted), | |||||
backgroundThread (thread), | |||||
numberOfSamplesToBuffer (jmax (1024, bufferSizeSamples)), | |||||
numberOfChannels (numChannels), | |||||
bufferValidStart (0), | |||||
bufferValidEnd (0), | |||||
nextPlayPos (0), | |||||
sampleRate (0), | |||||
wasSourceLooping (false), | |||||
isPrepared (false), | |||||
prefillBuffer (prefillBufferOnPrepareToPlay) | |||||
{ | |||||
jassert (source != nullptr); | |||||
jassert (numberOfSamplesToBuffer > 1024); // not much point using this class if you're | |||||
// not using a larger buffer.. | |||||
} | |||||
BufferingAudioSource::~BufferingAudioSource() | |||||
{ | |||||
releaseResources(); | |||||
} | |||||
//============================================================================== | |||||
void BufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double newSampleRate) | |||||
{ | |||||
const int bufferSizeNeeded = jmax (samplesPerBlockExpected * 2, numberOfSamplesToBuffer); | |||||
if (newSampleRate != sampleRate | |||||
|| bufferSizeNeeded != buffer.getNumSamples() | |||||
|| ! isPrepared) | |||||
{ | |||||
backgroundThread.removeTimeSliceClient (this); | |||||
isPrepared = true; | |||||
sampleRate = newSampleRate; | |||||
source->prepareToPlay (samplesPerBlockExpected, newSampleRate); | |||||
buffer.setSize (numberOfChannels, bufferSizeNeeded); | |||||
buffer.clear(); | |||||
bufferValidStart = 0; | |||||
bufferValidEnd = 0; | |||||
backgroundThread.addTimeSliceClient (this); | |||||
do | |||||
{ | |||||
backgroundThread.moveToFrontOfQueue (this); | |||||
Thread::sleep (5); | |||||
} | |||||
while (prefillBuffer | |||||
&& (bufferValidEnd - bufferValidStart < jmin (((int) newSampleRate) / 4, buffer.getNumSamples() / 2))); | |||||
} | |||||
} | |||||
void BufferingAudioSource::releaseResources() | |||||
{ | |||||
isPrepared = false; | |||||
backgroundThread.removeTimeSliceClient (this); | |||||
buffer.setSize (numberOfChannels, 0); | |||||
// MSVC2015 seems to need this if statement to not generate a warning during linking. | |||||
// As source is set in the constructor, there is no way that source could | |||||
// ever equal this, but it seems to make MSVC2015 happy. | |||||
if (source != this) | |||||
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; | |||||
} | |||||
} | |||||
bool BufferingAudioSource::waitForNextAudioBlockReady (const AudioSourceChannelInfo& info, const uint32 timeout) | |||||
{ | |||||
if (!source || source->getTotalLength() <= 0) | |||||
return false; | |||||
if (nextPlayPos + info.numSamples < 0) | |||||
return true; | |||||
if (! isLooping() && nextPlayPos > getTotalLength()) | |||||
return true; | |||||
uint32 now = Time::getMillisecondCounter(); | |||||
const uint32 startTime = now; | |||||
uint32 elapsed = (now >= startTime ? now - startTime | |||||
: (std::numeric_limits<uint32>::max() - startTime) + now); | |||||
while (elapsed <= timeout) | |||||
{ | |||||
{ | |||||
const ScopedLock sl (bufferStartPosLock); | |||||
const int validStart = static_cast<int> (jlimit (bufferValidStart, bufferValidEnd, nextPlayPos) - nextPlayPos); | |||||
const int validEnd = static_cast<int> (jlimit (bufferValidStart, bufferValidEnd, nextPlayPos + info.numSamples) - nextPlayPos); | |||||
if (validStart <= 0 && validStart < validEnd && validEnd >= info.numSamples) | |||||
return true; | |||||
} | |||||
if (elapsed < timeout && (! bufferReadyEvent.wait (static_cast<int> (timeout - elapsed)))) | |||||
return false; | |||||
now = Time::getMillisecondCounter(); | |||||
elapsed = (now >= startTime ? now - startTime | |||||
: (std::numeric_limits<uint32>::max() - startTime) + now); | |||||
} | |||||
return false; | |||||
} | |||||
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; | |||||
} | |||||
bufferReadyEvent.signal(); | |||||
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; | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,117 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
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 BufferingAudioSources 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 | |||||
@param prefillBufferOnPrepareToPlay if true, then calling prepareToPlay on this object will | |||||
block until the buffer has been filled | |||||
*/ | |||||
BufferingAudioSource (PositionableAudioSource* source, | |||||
TimeSliceThread& backgroundThread, | |||||
bool deleteSourceWhenDeleted, | |||||
int numberOfSamplesToBuffer, | |||||
int numberOfChannels = 2, | |||||
bool prefillBufferOnPrepareToPlay = true); | |||||
/** Destructor. | |||||
The input source may be deleted depending on whether the deleteSourceWhenDeleted | |||||
flag was set in the constructor. | |||||
*/ | |||||
~BufferingAudioSource(); | |||||
//============================================================================== | |||||
/** Implementation of the AudioSource method. */ | |||||
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; | |||||
/** Implementation of the AudioSource method. */ | |||||
void releaseResources() override; | |||||
/** Implementation of the AudioSource method. */ | |||||
void getNextAudioBlock (const AudioSourceChannelInfo&) override; | |||||
//============================================================================== | |||||
/** Implements the PositionableAudioSource method. */ | |||||
void setNextReadPosition (int64 newPosition) override; | |||||
/** Implements the PositionableAudioSource method. */ | |||||
int64 getNextReadPosition() const override; | |||||
/** Implements the PositionableAudioSource method. */ | |||||
int64 getTotalLength() const override { return source->getTotalLength(); } | |||||
/** Implements the PositionableAudioSource method. */ | |||||
bool isLooping() const override { return source->isLooping(); } | |||||
/** A useful function to block until the next the buffer info can be filled. | |||||
This is useful for offline rendering. | |||||
*/ | |||||
bool waitForNextAudioBlockReady (const AudioSourceChannelInfo& info, const uint32 timeout); | |||||
private: | |||||
//============================================================================== | |||||
OptionalScopedPointer<PositionableAudioSource> source; | |||||
TimeSliceThread& backgroundThread; | |||||
int numberOfSamplesToBuffer, numberOfChannels; | |||||
AudioSampleBuffer buffer; | |||||
CriticalSection bufferStartPosLock; | |||||
WaitableEvent bufferReadyEvent; | |||||
int64 volatile bufferValidStart, bufferValidEnd, nextPlayPos; | |||||
double volatile sampleRate; | |||||
bool wasSourceLooping, isPrepared, prefillBuffer; | |||||
bool readNextBufferChunk(); | |||||
void readBufferSection (int64 start, int length, int bufferOffset); | |||||
int useTimeSlice() override; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BufferingAudioSource) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,187 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
ChannelRemappingAudioSource::ChannelRemappingAudioSource (AudioSource* const source_, | |||||
const bool deleteSourceWhenDeleted) | |||||
: source (source_, deleteSourceWhenDeleted), | |||||
requiredNumberOfChannels (2) | |||||
{ | |||||
remappedInfo.buffer = &buffer; | |||||
remappedInfo.startSample = 0; | |||||
} | |||||
ChannelRemappingAudioSource::~ChannelRemappingAudioSource() {} | |||||
//============================================================================== | |||||
void ChannelRemappingAudioSource::setNumberOfChannelsToProduce (const int requiredNumberOfChannels_) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
requiredNumberOfChannels = requiredNumberOfChannels_; | |||||
} | |||||
void ChannelRemappingAudioSource::clearAllMappings() | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
remappedInputs.clear(); | |||||
remappedOutputs.clear(); | |||||
} | |||||
void ChannelRemappingAudioSource::setInputChannelMapping (const int destIndex, const int sourceIndex) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
while (remappedInputs.size() < destIndex) | |||||
remappedInputs.add (-1); | |||||
remappedInputs.set (destIndex, sourceIndex); | |||||
} | |||||
void ChannelRemappingAudioSource::setOutputChannelMapping (const int sourceIndex, const int destIndex) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
while (remappedOutputs.size() < sourceIndex) | |||||
remappedOutputs.add (-1); | |||||
remappedOutputs.set (sourceIndex, destIndex); | |||||
} | |||||
int ChannelRemappingAudioSource::getRemappedInputChannel (const int inputChannelIndex) const | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
if (inputChannelIndex >= 0 && inputChannelIndex < remappedInputs.size()) | |||||
return remappedInputs.getUnchecked (inputChannelIndex); | |||||
return -1; | |||||
} | |||||
int ChannelRemappingAudioSource::getRemappedOutputChannel (const int outputChannelIndex) const | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
if (outputChannelIndex >= 0 && outputChannelIndex < remappedOutputs.size()) | |||||
return remappedOutputs .getUnchecked (outputChannelIndex); | |||||
return -1; | |||||
} | |||||
//============================================================================== | |||||
void ChannelRemappingAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) | |||||
{ | |||||
source->prepareToPlay (samplesPerBlockExpected, sampleRate); | |||||
} | |||||
void ChannelRemappingAudioSource::releaseResources() | |||||
{ | |||||
source->releaseResources(); | |||||
} | |||||
void ChannelRemappingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
buffer.setSize (requiredNumberOfChannels, bufferToFill.numSamples, false, false, true); | |||||
const int numChans = bufferToFill.buffer->getNumChannels(); | |||||
for (int i = 0; i < buffer.getNumChannels(); ++i) | |||||
{ | |||||
const int remappedChan = getRemappedInputChannel (i); | |||||
if (remappedChan >= 0 && remappedChan < numChans) | |||||
{ | |||||
buffer.copyFrom (i, 0, *bufferToFill.buffer, | |||||
remappedChan, | |||||
bufferToFill.startSample, | |||||
bufferToFill.numSamples); | |||||
} | |||||
else | |||||
{ | |||||
buffer.clear (i, 0, bufferToFill.numSamples); | |||||
} | |||||
} | |||||
remappedInfo.numSamples = bufferToFill.numSamples; | |||||
source->getNextAudioBlock (remappedInfo); | |||||
bufferToFill.clearActiveBufferRegion(); | |||||
for (int i = 0; i < requiredNumberOfChannels; ++i) | |||||
{ | |||||
const int remappedChan = getRemappedOutputChannel (i); | |||||
if (remappedChan >= 0 && remappedChan < numChans) | |||||
{ | |||||
bufferToFill.buffer->addFrom (remappedChan, bufferToFill.startSample, | |||||
buffer, i, 0, bufferToFill.numSamples); | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
XmlElement* ChannelRemappingAudioSource::createXml() const | |||||
{ | |||||
XmlElement* e = new XmlElement ("MAPPINGS"); | |||||
String ins, outs; | |||||
const ScopedLock sl (lock); | |||||
for (int i = 0; i < remappedInputs.size(); ++i) | |||||
ins << remappedInputs.getUnchecked(i) << ' '; | |||||
for (int i = 0; i < remappedOutputs.size(); ++i) | |||||
outs << remappedOutputs.getUnchecked(i) << ' '; | |||||
e->setAttribute ("inputs", ins.trimEnd()); | |||||
e->setAttribute ("outputs", outs.trimEnd()); | |||||
return e; | |||||
} | |||||
void ChannelRemappingAudioSource::restoreFromXml (const XmlElement& e) | |||||
{ | |||||
if (e.hasTagName ("MAPPINGS")) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
clearAllMappings(); | |||||
StringArray ins, outs; | |||||
ins.addTokens (e.getStringAttribute ("inputs"), false); | |||||
outs.addTokens (e.getStringAttribute ("outputs"), false); | |||||
for (int i = 0; i < ins.size(); ++i) | |||||
remappedInputs.add (ins[i].getIntValue()); | |||||
for (int i = 0; i < outs.size(); ++i) | |||||
remappedOutputs.add (outs[i].getIntValue()); | |||||
} | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,139 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
An AudioSource that takes the audio from another source, and re-maps its | |||||
input and output channels to a different arrangement. | |||||
You can use this to increase or decrease the number of channels that an | |||||
audio source uses, or to re-order those channels. | |||||
Call the reset() method before using it to set up a default mapping, and then | |||||
the setInputChannelMapping() and setOutputChannelMapping() methods to | |||||
create an appropriate mapping, otherwise no channels will be connected and | |||||
it'll produce silence. | |||||
@see AudioSource | |||||
*/ | |||||
class ChannelRemappingAudioSource : public AudioSource | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a remapping source that will pass on audio from the given input. | |||||
@param source the input source to use. Make sure that this doesn't | |||||
get deleted before the ChannelRemappingAudioSource object | |||||
@param deleteSourceWhenDeleted if true, the input source will be deleted | |||||
when this object is deleted, if false, the caller is | |||||
responsible for its deletion | |||||
*/ | |||||
ChannelRemappingAudioSource (AudioSource* source, | |||||
bool deleteSourceWhenDeleted); | |||||
/** Destructor. */ | |||||
~ChannelRemappingAudioSource(); | |||||
//============================================================================== | |||||
/** Specifies a number of channels that this audio source must produce from its | |||||
getNextAudioBlock() callback. | |||||
*/ | |||||
void setNumberOfChannelsToProduce (int requiredNumberOfChannels); | |||||
/** Clears any mapped channels. | |||||
After this, no channels are mapped, so this object will produce silence. Create | |||||
some mappings with setInputChannelMapping() and setOutputChannelMapping(). | |||||
*/ | |||||
void clearAllMappings(); | |||||
/** Creates an input channel mapping. | |||||
When the getNextAudioBlock() method is called, the data in channel sourceChannelIndex of the incoming | |||||
data will be sent to destChannelIndex of our input source. | |||||
@param destChannelIndex the index of an input channel in our input audio source (i.e. the | |||||
source specified when this object was created). | |||||
@param sourceChannelIndex the index of the input channel in the incoming audio data buffer | |||||
during our getNextAudioBlock() callback | |||||
*/ | |||||
void setInputChannelMapping (int destChannelIndex, | |||||
int sourceChannelIndex); | |||||
/** Creates an output channel mapping. | |||||
When the getNextAudioBlock() method is called, the data returned in channel sourceChannelIndex by | |||||
our input audio source will be copied to channel destChannelIndex of the final buffer. | |||||
@param sourceChannelIndex the index of an output channel coming from our input audio source | |||||
(i.e. the source specified when this object was created). | |||||
@param destChannelIndex the index of the output channel in the incoming audio data buffer | |||||
during our getNextAudioBlock() callback | |||||
*/ | |||||
void setOutputChannelMapping (int sourceChannelIndex, | |||||
int destChannelIndex); | |||||
/** Returns the channel from our input that will be sent to channel inputChannelIndex of | |||||
our input audio source. | |||||
*/ | |||||
int getRemappedInputChannel (int inputChannelIndex) const; | |||||
/** Returns the output channel to which channel outputChannelIndex of our input audio | |||||
source will be sent to. | |||||
*/ | |||||
int getRemappedOutputChannel (int outputChannelIndex) const; | |||||
//============================================================================== | |||||
/** Returns an XML object to encapsulate the state of the mappings. | |||||
@see restoreFromXml | |||||
*/ | |||||
XmlElement* createXml() const; | |||||
/** Restores the mappings from an XML object created by createXML(). | |||||
@see createXml | |||||
*/ | |||||
void restoreFromXml (const XmlElement&); | |||||
//============================================================================== | |||||
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; | |||||
void releaseResources() override; | |||||
void getNextAudioBlock (const AudioSourceChannelInfo&) override; | |||||
private: | |||||
//============================================================================== | |||||
OptionalScopedPointer<AudioSource> source; | |||||
Array<int> remappedInputs, remappedOutputs; | |||||
int requiredNumberOfChannels; | |||||
AudioSampleBuffer buffer; | |||||
AudioSourceChannelInfo remappedInfo; | |||||
CriticalSection lock; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChannelRemappingAudioSource) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,80 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
IIRFilterAudioSource::IIRFilterAudioSource (AudioSource* const inputSource, | |||||
const bool deleteInputWhenDeleted) | |||||
: input (inputSource, deleteInputWhenDeleted) | |||||
{ | |||||
jassert (inputSource != nullptr); | |||||
for (int i = 2; --i >= 0;) | |||||
iirFilters.add (new IIRFilter()); | |||||
} | |||||
IIRFilterAudioSource::~IIRFilterAudioSource() {} | |||||
//============================================================================== | |||||
void IIRFilterAudioSource::setCoefficients (const IIRCoefficients& newCoefficients) | |||||
{ | |||||
for (int i = iirFilters.size(); --i >= 0;) | |||||
iirFilters.getUnchecked(i)->setCoefficients (newCoefficients); | |||||
} | |||||
void IIRFilterAudioSource::makeInactive() | |||||
{ | |||||
for (int i = iirFilters.size(); --i >= 0;) | |||||
iirFilters.getUnchecked(i)->makeInactive(); | |||||
} | |||||
//============================================================================== | |||||
void IIRFilterAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) | |||||
{ | |||||
input->prepareToPlay (samplesPerBlockExpected, sampleRate); | |||||
for (int i = iirFilters.size(); --i >= 0;) | |||||
iirFilters.getUnchecked(i)->reset(); | |||||
} | |||||
void IIRFilterAudioSource::releaseResources() | |||||
{ | |||||
input->releaseResources(); | |||||
} | |||||
void IIRFilterAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) | |||||
{ | |||||
input->getNextAudioBlock (bufferToFill); | |||||
const int numChannels = bufferToFill.buffer->getNumChannels(); | |||||
while (numChannels > iirFilters.size()) | |||||
iirFilters.add (new IIRFilter (*iirFilters.getUnchecked (0))); | |||||
for (int i = 0; i < numChannels; ++i) | |||||
iirFilters.getUnchecked(i) | |||||
->processSamples (bufferToFill.buffer->getWritePointer (i, bufferToFill.startSample), | |||||
bufferToFill.numSamples); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,66 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
An AudioSource that performs an IIR filter on another source. | |||||
*/ | |||||
class JUCE_API IIRFilterAudioSource : public AudioSource | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a IIRFilterAudioSource for a given input source. | |||||
@param inputSource the input source to read from - this must not be null | |||||
@param deleteInputWhenDeleted if true, the input source will be deleted when | |||||
this object is deleted | |||||
*/ | |||||
IIRFilterAudioSource (AudioSource* inputSource, | |||||
bool deleteInputWhenDeleted); | |||||
/** Destructor. */ | |||||
~IIRFilterAudioSource(); | |||||
//============================================================================== | |||||
/** Changes the filter to use the same parameters as the one being passed in. */ | |||||
void setCoefficients (const IIRCoefficients& newCoefficients); | |||||
/** Calls IIRFilter::makeInactive() on all the filters being used internally. */ | |||||
void makeInactive(); | |||||
//============================================================================== | |||||
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; | |||||
void releaseResources() override; | |||||
void getNextAudioBlock (const AudioSourceChannelInfo&) override; | |||||
private: | |||||
//============================================================================== | |||||
OptionalScopedPointer<AudioSource> input; | |||||
OwnedArray<IIRFilter> iirFilters; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (IIRFilterAudioSource) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,70 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
MemoryAudioSource::MemoryAudioSource (AudioBuffer<float>& bufferToUse, bool copyMemory, bool shouldLoop) | |||||
: isLooping (shouldLoop) | |||||
{ | |||||
if (copyMemory) | |||||
buffer.makeCopyOf (bufferToUse); | |||||
else | |||||
buffer.setDataToReferTo (bufferToUse.getArrayOfWritePointers(), | |||||
bufferToUse.getNumChannels(), | |||||
bufferToUse.getNumSamples()); | |||||
} | |||||
//============================================================================== | |||||
void MemoryAudioSource::prepareToPlay (int /*samplesPerBlockExpected*/, double /*sampleRate*/) | |||||
{ | |||||
position = 0; | |||||
} | |||||
void MemoryAudioSource::releaseResources() {} | |||||
void MemoryAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) | |||||
{ | |||||
auto& dst = *bufferToFill.buffer; | |||||
auto channels = jmin (dst.getNumChannels(), buffer.getNumChannels()); | |||||
auto max = 0, pos = 0; | |||||
auto n = buffer.getNumSamples(), m = bufferToFill.numSamples; | |||||
for (auto i = position; (i < n || isLooping) && (pos < m); i += max) | |||||
{ | |||||
max = jmin (m - pos, n - (i % n)); | |||||
int ch = 0; | |||||
for (; ch < channels; ++ch) | |||||
dst.copyFrom (ch, bufferToFill.startSample + pos, buffer, ch, i % n, max); | |||||
for (; ch < dst.getNumChannels(); ++ch) | |||||
dst.clear (ch, bufferToFill.startSample + pos, max); | |||||
pos += max; | |||||
} | |||||
if (pos < m) | |||||
dst.clear (bufferToFill.startSample + pos, m - pos); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,63 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
An AudioSource which takes some float audio data as an input. | |||||
*/ | |||||
class JUCE_API MemoryAudioSource : public AudioSource | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a MemoryAudioSource by providing an audio buffer. | |||||
If copyMemory is true then the buffer will be copied into an internal | |||||
buffer which will be owned by the MemoryAudioSource. If copyMemory is | |||||
false, then you must ensure that the lifetime of the audio buffer is | |||||
at least as long as the MemoryAudioSource. | |||||
*/ | |||||
MemoryAudioSource (AudioBuffer<float>& audioBuffer, bool copyMemory, bool shouldLoop = false); | |||||
//============================================================================== | |||||
/** 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; | |||||
private: | |||||
//============================================================================== | |||||
AudioBuffer<float> buffer; | |||||
int position = 0; | |||||
bool isLooping; | |||||
//============================================================================== | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryAudioSource) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,158 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
MixerAudioSource::MixerAudioSource() | |||||
: currentSampleRate (0.0), bufferSizeExpected (0) | |||||
{ | |||||
} | |||||
MixerAudioSource::~MixerAudioSource() | |||||
{ | |||||
removeAllInputs(); | |||||
} | |||||
//============================================================================== | |||||
void MixerAudioSource::addInputSource (AudioSource* input, const bool deleteWhenRemoved) | |||||
{ | |||||
if (input != nullptr && ! inputs.contains (input)) | |||||
{ | |||||
double localRate; | |||||
int localBufferSize; | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
localRate = currentSampleRate; | |||||
localBufferSize = bufferSizeExpected; | |||||
} | |||||
if (localRate > 0.0) | |||||
input->prepareToPlay (localBufferSize, localRate); | |||||
const ScopedLock sl (lock); | |||||
inputsToDelete.setBit (inputs.size(), deleteWhenRemoved); | |||||
inputs.add (input); | |||||
} | |||||
} | |||||
void MixerAudioSource::removeInputSource (AudioSource* const input) | |||||
{ | |||||
if (input != nullptr) | |||||
{ | |||||
ScopedPointer<AudioSource> toDelete; | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
const int index = inputs.indexOf (input); | |||||
if (index < 0) | |||||
return; | |||||
if (inputsToDelete [index]) | |||||
toDelete = input; | |||||
inputsToDelete.shiftBits (-1, index); | |||||
inputs.remove (index); | |||||
} | |||||
input->releaseResources(); | |||||
} | |||||
} | |||||
void MixerAudioSource::removeAllInputs() | |||||
{ | |||||
OwnedArray<AudioSource> toDelete; | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
for (int i = inputs.size(); --i >= 0;) | |||||
if (inputsToDelete[i]) | |||||
toDelete.add (inputs.getUnchecked(i)); | |||||
inputs.clear(); | |||||
} | |||||
for (int i = toDelete.size(); --i >= 0;) | |||||
toDelete.getUnchecked(i)->releaseResources(); | |||||
} | |||||
void MixerAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) | |||||
{ | |||||
tempBuffer.setSize (2, samplesPerBlockExpected); | |||||
const ScopedLock sl (lock); | |||||
currentSampleRate = sampleRate; | |||||
bufferSizeExpected = samplesPerBlockExpected; | |||||
for (int i = inputs.size(); --i >= 0;) | |||||
inputs.getUnchecked(i)->prepareToPlay (samplesPerBlockExpected, sampleRate); | |||||
} | |||||
void MixerAudioSource::releaseResources() | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
for (int i = inputs.size(); --i >= 0;) | |||||
inputs.getUnchecked(i)->releaseResources(); | |||||
tempBuffer.setSize (2, 0); | |||||
currentSampleRate = 0; | |||||
bufferSizeExpected = 0; | |||||
} | |||||
void MixerAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
if (inputs.size() > 0) | |||||
{ | |||||
inputs.getUnchecked(0)->getNextAudioBlock (info); | |||||
if (inputs.size() > 1) | |||||
{ | |||||
tempBuffer.setSize (jmax (1, info.buffer->getNumChannels()), | |||||
info.buffer->getNumSamples()); | |||||
AudioSourceChannelInfo info2 (&tempBuffer, 0, info.numSamples); | |||||
for (int i = 1; i < inputs.size(); ++i) | |||||
{ | |||||
inputs.getUnchecked(i)->getNextAudioBlock (info2); | |||||
for (int chan = 0; chan < info.buffer->getNumChannels(); ++chan) | |||||
info.buffer->addFrom (chan, info.startSample, tempBuffer, chan, 0, info.numSamples); | |||||
} | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
info.clearActiveBufferRegion(); | |||||
} | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,97 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
An AudioSource that mixes together the output of a set of other AudioSources. | |||||
Input sources can be added and removed while the mixer is running as long as their | |||||
prepareToPlay() and releaseResources() methods are called before and after adding | |||||
them to the mixer. | |||||
*/ | |||||
class JUCE_API MixerAudioSource : public AudioSource | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a MixerAudioSource. */ | |||||
MixerAudioSource(); | |||||
/** Destructor. */ | |||||
~MixerAudioSource(); | |||||
//============================================================================== | |||||
/** Adds an input source to the mixer. | |||||
If the mixer is running you'll need to make sure that the input source | |||||
is ready to play by calling its prepareToPlay() method before adding it. | |||||
If the mixer is stopped, then its input sources will be automatically | |||||
prepared when the mixer's prepareToPlay() method is called. | |||||
@param newInput the source to add to the mixer | |||||
@param deleteWhenRemoved if true, then this source will be deleted when | |||||
no longer needed by the mixer. | |||||
*/ | |||||
void addInputSource (AudioSource* newInput, bool deleteWhenRemoved); | |||||
/** Removes an input source. | |||||
If the source was added by calling addInputSource() with the deleteWhenRemoved | |||||
flag set, it will be deleted by this method. | |||||
*/ | |||||
void removeInputSource (AudioSource* input); | |||||
/** Removes all the input sources. | |||||
Any sources which were added by calling addInputSource() with the deleteWhenRemoved | |||||
flag set will be deleted by this method. | |||||
*/ | |||||
void removeAllInputs(); | |||||
//============================================================================== | |||||
/** Implementation of the AudioSource method. | |||||
This will call prepareToPlay() on all its input sources. | |||||
*/ | |||||
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; | |||||
/** Implementation of the AudioSource method. | |||||
This will call releaseResources() on all its input sources. | |||||
*/ | |||||
void releaseResources() override; | |||||
/** Implementation of the AudioSource method. */ | |||||
void getNextAudioBlock (const AudioSourceChannelInfo&) override; | |||||
private: | |||||
//============================================================================== | |||||
Array<AudioSource*> inputs; | |||||
BigInteger inputsToDelete; | |||||
CriticalSection lock; | |||||
AudioSampleBuffer tempBuffer; | |||||
double currentSampleRate; | |||||
int bufferSizeExpected; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MixerAudioSource) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,74 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
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) { ignoreUnused (shouldLoop); } | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,266 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
ResamplingAudioSource::ResamplingAudioSource (AudioSource* const inputSource, | |||||
const bool deleteInputWhenDeleted, | |||||
const int channels) | |||||
: input (inputSource, deleteInputWhenDeleted), | |||||
ratio (1.0), | |||||
lastRatio (1.0), | |||||
bufferPos (0), | |||||
sampsInBuffer (0), | |||||
subSampleOffset (0), | |||||
numChannels (channels) | |||||
{ | |||||
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); | |||||
const int scaledBlockSize = roundToInt (samplesPerBlockExpected * ratio); | |||||
input->prepareToPlay (scaledBlockSize, sampleRate * ratio); | |||||
buffer.setSize (numChannels, scaledBlockSize + 32); | |||||
filterStates.calloc ((size_t) numChannels); | |||||
srcBuffers.calloc ((size_t) numChannels); | |||||
destBuffers.calloc ((size_t) numChannels); | |||||
createLowPass (ratio); | |||||
flushBuffers(); | |||||
} | |||||
void ResamplingAudioSource::flushBuffers() | |||||
{ | |||||
buffer.clear(); | |||||
bufferPos = 0; | |||||
sampsInBuffer = 0; | |||||
subSampleOffset = 0.0; | |||||
resetFilters(); | |||||
} | |||||
void ResamplingAudioSource::releaseResources() | |||||
{ | |||||
input->releaseResources(); | |||||
buffer.setSize (numChannels, 0); | |||||
} | |||||
void ResamplingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) | |||||
{ | |||||
double localRatio; | |||||
{ | |||||
const SpinLock::ScopedLockType sl (ratioLock); | |||||
localRatio = ratio; | |||||
} | |||||
if (lastRatio != localRatio) | |||||
{ | |||||
createLowPass (localRatio); | |||||
lastRatio = localRatio; | |||||
} | |||||
const int sampsNeeded = roundToInt (info.numSamples * localRatio) + 3; | |||||
int bufferSize = buffer.getNumSamples(); | |||||
if (bufferSize < sampsNeeded + 8) | |||||
{ | |||||
bufferPos %= bufferSize; | |||||
bufferSize = sampsNeeded + 32; | |||||
buffer.setSize (buffer.getNumChannels(), bufferSize, true, true); | |||||
} | |||||
bufferPos %= bufferSize; | |||||
int endOfBufferPos = bufferPos + sampsInBuffer; | |||||
const int channelsToProcess = jmin (numChannels, info.buffer->getNumChannels()); | |||||
while (sampsNeeded > sampsInBuffer) | |||||
{ | |||||
endOfBufferPos %= bufferSize; | |||||
int numToDo = jmin (sampsNeeded - sampsInBuffer, | |||||
bufferSize - endOfBufferPos); | |||||
AudioSourceChannelInfo readInfo (&buffer, endOfBufferPos, numToDo); | |||||
input->getNextAudioBlock (readInfo); | |||||
if (localRatio > 1.0001) | |||||
{ | |||||
// for down-sampling, pre-apply the filter.. | |||||
for (int i = channelsToProcess; --i >= 0;) | |||||
applyFilter (buffer.getWritePointer (i, endOfBufferPos), numToDo, filterStates[i]); | |||||
} | |||||
sampsInBuffer += numToDo; | |||||
endOfBufferPos += numToDo; | |||||
} | |||||
for (int channel = 0; channel < channelsToProcess; ++channel) | |||||
{ | |||||
destBuffers[channel] = info.buffer->getWritePointer (channel, info.startSample); | |||||
srcBuffers[channel] = buffer.getReadPointer (channel); | |||||
} | |||||
int nextPos = (bufferPos + 1) % bufferSize; | |||||
for (int m = info.numSamples; --m >= 0;) | |||||
{ | |||||
jassert (sampsInBuffer > 0 && nextPos != endOfBufferPos); | |||||
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; | |||||
while (subSampleOffset >= 1.0) | |||||
{ | |||||
if (++bufferPos >= bufferSize) | |||||
bufferPos = 0; | |||||
--sampsInBuffer; | |||||
nextPos = (bufferPos + 1) % bufferSize; | |||||
subSampleOffset -= 1.0; | |||||
} | |||||
} | |||||
if (localRatio < 0.9999) | |||||
{ | |||||
// for up-sampling, apply the filter after transposing.. | |||||
for (int i = channelsToProcess; --i >= 0;) | |||||
applyFilter (info.buffer->getWritePointer (i, info.startSample), info.numSamples, filterStates[i]); | |||||
} | |||||
else if (localRatio <= 1.0001 && info.numSamples > 0) | |||||
{ | |||||
// if the filter's not currently being applied, keep it stoked with the last couple of samples to avoid discontinuities | |||||
for (int i = channelsToProcess; --i >= 0;) | |||||
{ | |||||
const float* const endOfBuffer = info.buffer->getReadPointer (i, info.startSample + info.numSamples - 1); | |||||
FilterState& fs = filterStates[i]; | |||||
if (info.numSamples > 1) | |||||
{ | |||||
fs.y2 = fs.x2 = *(endOfBuffer - 1); | |||||
} | |||||
else | |||||
{ | |||||
fs.y2 = fs.y1; | |||||
fs.x2 = fs.x1; | |||||
} | |||||
fs.y1 = fs.x1 = *endOfBuffer; | |||||
} | |||||
} | |||||
jassert (sampsInBuffer >= 0); | |||||
} | |||||
void ResamplingAudioSource::createLowPass (const double frequencyRatio) | |||||
{ | |||||
const double proportionalRate = (frequencyRatio > 1.0) ? 0.5 / frequencyRatio | |||||
: 0.5 * frequencyRatio; | |||||
const double n = 1.0 / std::tan (double_Pi * jmax (0.001, proportionalRate)); | |||||
const double nSquared = n * n; | |||||
const double c1 = 1.0 / (1.0 + std::sqrt (2.0) * n + nSquared); | |||||
setFilterCoefficients (c1, | |||||
c1 * 2.0f, | |||||
c1, | |||||
1.0, | |||||
c1 * 2.0 * (1.0 - nSquared), | |||||
c1 * (1.0 - std::sqrt (2.0) * n + nSquared)); | |||||
} | |||||
void ResamplingAudioSource::setFilterCoefficients (double c1, double c2, double c3, double c4, double c5, double c6) | |||||
{ | |||||
const double a = 1.0 / c4; | |||||
c1 *= a; | |||||
c2 *= a; | |||||
c3 *= a; | |||||
c5 *= a; | |||||
c6 *= a; | |||||
coefficients[0] = c1; | |||||
coefficients[1] = c2; | |||||
coefficients[2] = c3; | |||||
coefficients[3] = c4; | |||||
coefficients[4] = c5; | |||||
coefficients[5] = c6; | |||||
} | |||||
void ResamplingAudioSource::resetFilters() | |||||
{ | |||||
if (filterStates != nullptr) | |||||
filterStates.clear ((size_t) numChannels); | |||||
} | |||||
void ResamplingAudioSource::applyFilter (float* samples, int num, FilterState& fs) | |||||
{ | |||||
while (--num >= 0) | |||||
{ | |||||
const double in = *samples; | |||||
double out = coefficients[0] * in | |||||
+ coefficients[1] * fs.x1 | |||||
+ coefficients[2] * fs.x2 | |||||
- coefficients[4] * fs.y1 | |||||
- coefficients[5] * fs.y2; | |||||
#if JUCE_INTEL | |||||
if (! (out < -1.0e-8 || out > 1.0e-8)) | |||||
out = 0; | |||||
#endif | |||||
fs.x2 = fs.x1; | |||||
fs.x1 = in; | |||||
fs.y2 = fs.y1; | |||||
fs.y1 = out; | |||||
*samples++ = (float) out; | |||||
} | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,103 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
A type of AudioSource that takes an input source and changes its sample rate. | |||||
@see AudioSource, LagrangeInterpolator, CatmullRomInterpolator | |||||
*/ | |||||
class JUCE_API ResamplingAudioSource : public AudioSource | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a ResamplingAudioSource for a given input source. | |||||
@param inputSource the input source to read from | |||||
@param deleteInputWhenDeleted if true, the input source will be deleted when | |||||
this object is deleted | |||||
@param numChannels the number of channels to process | |||||
*/ | |||||
ResamplingAudioSource (AudioSource* inputSource, | |||||
bool deleteInputWhenDeleted, | |||||
int numChannels = 2); | |||||
/** Destructor. */ | |||||
~ResamplingAudioSource(); | |||||
/** Changes the resampling ratio. | |||||
(This value can be changed at any time, even while the source is running). | |||||
@param samplesInPerOutputSample if set to 1.0, the input is passed through; higher | |||||
values will speed it up; lower values will slow it | |||||
down. The ratio must be greater than 0 | |||||
*/ | |||||
void setResamplingRatio (double samplesInPerOutputSample); | |||||
/** Returns the current resampling ratio. | |||||
This is the value that was set by setResamplingRatio(). | |||||
*/ | |||||
double getResamplingRatio() const noexcept { return ratio; } | |||||
/** Clears any buffers and filters that the resampler is using. */ | |||||
void flushBuffers(); | |||||
//============================================================================== | |||||
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; | |||||
void releaseResources() override; | |||||
void getNextAudioBlock (const AudioSourceChannelInfo&) override; | |||||
private: | |||||
//============================================================================== | |||||
OptionalScopedPointer<AudioSource> input; | |||||
double ratio, lastRatio; | |||||
AudioSampleBuffer buffer; | |||||
int bufferPos, sampsInBuffer; | |||||
double subSampleOffset; | |||||
double coefficients[6]; | |||||
SpinLock ratioLock; | |||||
const int numChannels; | |||||
HeapBlock<float*> destBuffers; | |||||
HeapBlock<const float*> srcBuffers; | |||||
void setFilterCoefficients (double c1, double c2, double c3, double c4, double c5, double c6); | |||||
void createLowPass (double proportionalRate); | |||||
struct FilterState | |||||
{ | |||||
double x1, x2, y1, y2; | |||||
}; | |||||
HeapBlock<FilterState> filterStates; | |||||
void resetFilters(); | |||||
void applyFilter (float* samples, int num, FilterState& fs); | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ResamplingAudioSource) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,83 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
ReverbAudioSource::ReverbAudioSource (AudioSource* const inputSource, const bool deleteInputWhenDeleted) | |||||
: input (inputSource, deleteInputWhenDeleted), | |||||
bypass (false) | |||||
{ | |||||
jassert (inputSource != nullptr); | |||||
} | |||||
ReverbAudioSource::~ReverbAudioSource() {} | |||||
void ReverbAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
input->prepareToPlay (samplesPerBlockExpected, sampleRate); | |||||
reverb.setSampleRate (sampleRate); | |||||
} | |||||
void ReverbAudioSource::releaseResources() {} | |||||
void ReverbAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
input->getNextAudioBlock (bufferToFill); | |||||
if (! bypass) | |||||
{ | |||||
float* const firstChannel = bufferToFill.buffer->getWritePointer (0, bufferToFill.startSample); | |||||
if (bufferToFill.buffer->getNumChannels() > 1) | |||||
{ | |||||
reverb.processStereo (firstChannel, | |||||
bufferToFill.buffer->getWritePointer (1, bufferToFill.startSample), | |||||
bufferToFill.numSamples); | |||||
} | |||||
else | |||||
{ | |||||
reverb.processMono (firstChannel, bufferToFill.numSamples); | |||||
} | |||||
} | |||||
} | |||||
void ReverbAudioSource::setParameters (const Reverb::Parameters& newParams) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
reverb.setParameters (newParams); | |||||
} | |||||
void ReverbAudioSource::setBypassed (bool b) noexcept | |||||
{ | |||||
if (bypass != b) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
bypass = b; | |||||
reverb.reset(); | |||||
} | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,72 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
An AudioSource that uses the Reverb class to apply a reverb to another AudioSource. | |||||
@see Reverb | |||||
*/ | |||||
class JUCE_API ReverbAudioSource : public AudioSource | |||||
{ | |||||
public: | |||||
/** Creates a ReverbAudioSource to process a given input source. | |||||
@param inputSource the input source to read from - this must not be null | |||||
@param deleteInputWhenDeleted if true, the input source will be deleted when | |||||
this object is deleted | |||||
*/ | |||||
ReverbAudioSource (AudioSource* inputSource, | |||||
bool deleteInputWhenDeleted); | |||||
/** Destructor. */ | |||||
~ReverbAudioSource(); | |||||
//============================================================================== | |||||
/** Returns the parameters from the reverb. */ | |||||
const Reverb::Parameters& getParameters() const noexcept { return reverb.getParameters(); } | |||||
/** Changes the reverb's parameters. */ | |||||
void setParameters (const Reverb::Parameters& newParams); | |||||
void setBypassed (bool isBypassed) noexcept; | |||||
bool isBypassed() const noexcept { return bypass; } | |||||
//============================================================================== | |||||
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; | |||||
void releaseResources() override; | |||||
void getNextAudioBlock (const AudioSourceChannelInfo&) override; | |||||
private: | |||||
//============================================================================== | |||||
CriticalSection lock; | |||||
OptionalScopedPointer<AudioSource> input; | |||||
Reverb reverb; | |||||
volatile bool bypass; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ReverbAudioSource) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,78 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
ToneGeneratorAudioSource::ToneGeneratorAudioSource() | |||||
: frequency (1000.0), | |||||
sampleRate (44100.0), | |||||
currentPhase (0.0), | |||||
phasePerSample (0.0), | |||||
amplitude (0.5f) | |||||
{ | |||||
} | |||||
ToneGeneratorAudioSource::~ToneGeneratorAudioSource() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
void ToneGeneratorAudioSource::setAmplitude (const float newAmplitude) | |||||
{ | |||||
amplitude = newAmplitude; | |||||
} | |||||
void ToneGeneratorAudioSource::setFrequency (const double newFrequencyHz) | |||||
{ | |||||
frequency = newFrequencyHz; | |||||
phasePerSample = 0.0; | |||||
} | |||||
//============================================================================== | |||||
void ToneGeneratorAudioSource::prepareToPlay (int /*samplesPerBlockExpected*/, double rate) | |||||
{ | |||||
currentPhase = 0.0; | |||||
phasePerSample = 0.0; | |||||
sampleRate = rate; | |||||
} | |||||
void ToneGeneratorAudioSource::releaseResources() | |||||
{ | |||||
} | |||||
void ToneGeneratorAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) | |||||
{ | |||||
if (phasePerSample == 0.0) | |||||
phasePerSample = double_Pi * 2.0 / (sampleRate / frequency); | |||||
for (int i = 0; i < info.numSamples; ++i) | |||||
{ | |||||
const float sample = amplitude * (float) std::sin (currentPhase); | |||||
currentPhase += phasePerSample; | |||||
for (int j = info.buffer->getNumChannels(); --j >= 0;) | |||||
info.buffer->setSample (j, info.startSample + i, sample); | |||||
} | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,69 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
A simple AudioSource that generates a sine wave. | |||||
*/ | |||||
class JUCE_API ToneGeneratorAudioSource : public AudioSource | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a ToneGeneratorAudioSource. */ | |||||
ToneGeneratorAudioSource(); | |||||
/** Destructor. */ | |||||
~ToneGeneratorAudioSource(); | |||||
//============================================================================== | |||||
/** Sets the signal's amplitude. */ | |||||
void setAmplitude (float newAmplitude); | |||||
/** Sets the signal's frequency. */ | |||||
void setFrequency (double newFrequencyHz); | |||||
//============================================================================== | |||||
/** Implementation of the AudioSource method. */ | |||||
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; | |||||
/** Implementation of the AudioSource method. */ | |||||
void releaseResources() override; | |||||
/** Implementation of the AudioSource method. */ | |||||
void getNextAudioBlock (const AudioSourceChannelInfo&) override; | |||||
private: | |||||
//============================================================================== | |||||
double frequency, sampleRate; | |||||
double currentPhase, phasePerSample; | |||||
float amplitude; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToneGeneratorAudioSource) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,574 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
SynthesiserSound::SynthesiserSound() {} | |||||
SynthesiserSound::~SynthesiserSound() {} | |||||
//============================================================================== | |||||
SynthesiserVoice::SynthesiserVoice() {} | |||||
SynthesiserVoice::~SynthesiserVoice() {} | |||||
bool SynthesiserVoice::isPlayingChannel (const int midiChannel) const | |||||
{ | |||||
return currentPlayingMidiChannel == midiChannel; | |||||
} | |||||
void SynthesiserVoice::setCurrentPlaybackSampleRate (const double newRate) | |||||
{ | |||||
currentSampleRate = newRate; | |||||
} | |||||
bool SynthesiserVoice::isVoiceActive() const | |||||
{ | |||||
return getCurrentlyPlayingNote() >= 0; | |||||
} | |||||
void SynthesiserVoice::clearCurrentNote() | |||||
{ | |||||
currentlyPlayingNote = -1; | |||||
currentlyPlayingSound = nullptr; | |||||
currentPlayingMidiChannel = 0; | |||||
} | |||||
void SynthesiserVoice::aftertouchChanged (int) {} | |||||
void SynthesiserVoice::channelPressureChanged (int) {} | |||||
bool SynthesiserVoice::wasStartedBefore (const SynthesiserVoice& other) const noexcept | |||||
{ | |||||
return noteOnTime < other.noteOnTime; | |||||
} | |||||
void SynthesiserVoice::renderNextBlock (AudioBuffer<double>& outputBuffer, | |||||
int startSample, int numSamples) | |||||
{ | |||||
AudioBuffer<double> subBuffer (outputBuffer.getArrayOfWritePointers(), | |||||
outputBuffer.getNumChannels(), | |||||
startSample, numSamples); | |||||
tempBuffer.makeCopyOf (subBuffer, true); | |||||
renderNextBlock (tempBuffer, 0, numSamples); | |||||
subBuffer.makeCopyOf (tempBuffer, true); | |||||
} | |||||
//============================================================================== | |||||
Synthesiser::Synthesiser() | |||||
{ | |||||
for (int i = 0; i < numElementsInArray (lastPitchWheelValues); ++i) | |||||
lastPitchWheelValues[i] = 0x2000; | |||||
} | |||||
Synthesiser::~Synthesiser() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
SynthesiserVoice* Synthesiser::getVoice (const int index) const | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
return voices [index]; | |||||
} | |||||
void Synthesiser::clearVoices() | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
voices.clear(); | |||||
} | |||||
SynthesiserVoice* Synthesiser::addVoice (SynthesiserVoice* const newVoice) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
newVoice->setCurrentPlaybackSampleRate (sampleRate); | |||||
return voices.add (newVoice); | |||||
} | |||||
void Synthesiser::removeVoice (const int index) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
voices.remove (index); | |||||
} | |||||
void Synthesiser::clearSounds() | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
sounds.clear(); | |||||
} | |||||
SynthesiserSound* Synthesiser::addSound (const SynthesiserSound::Ptr& newSound) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
return sounds.add (newSound); | |||||
} | |||||
void Synthesiser::removeSound (const int index) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
sounds.remove (index); | |||||
} | |||||
void Synthesiser::setNoteStealingEnabled (const bool shouldSteal) | |||||
{ | |||||
shouldStealNotes = shouldSteal; | |||||
} | |||||
void Synthesiser::setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict) noexcept | |||||
{ | |||||
jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1 | |||||
minimumSubBlockSize = numSamples; | |||||
subBlockSubdivisionIsStrict = shouldBeStrict; | |||||
} | |||||
//============================================================================== | |||||
void Synthesiser::setCurrentPlaybackSampleRate (const double newRate) | |||||
{ | |||||
if (sampleRate != newRate) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
allNotesOff (0, false); | |||||
sampleRate = newRate; | |||||
for (auto* voice : voices) | |||||
voice->setCurrentPlaybackSampleRate (newRate); | |||||
} | |||||
} | |||||
template <typename floatType> | |||||
void Synthesiser::processNextBlock (AudioBuffer<floatType>& outputAudio, | |||||
const MidiBuffer& midiData, | |||||
int startSample, | |||||
int numSamples) | |||||
{ | |||||
// must set the sample rate before using this! | |||||
jassert (sampleRate != 0); | |||||
const int targetChannels = outputAudio.getNumChannels(); | |||||
MidiBuffer::Iterator midiIterator (midiData); | |||||
midiIterator.setNextSamplePosition (startSample); | |||||
bool firstEvent = true; | |||||
int midiEventPos; | |||||
MidiMessage m; | |||||
const ScopedLock sl (lock); | |||||
while (numSamples > 0) | |||||
{ | |||||
if (! midiIterator.getNextEvent (m, midiEventPos)) | |||||
{ | |||||
if (targetChannels > 0) | |||||
renderVoices (outputAudio, startSample, numSamples); | |||||
return; | |||||
} | |||||
const int samplesToNextMidiMessage = midiEventPos - startSample; | |||||
if (samplesToNextMidiMessage >= numSamples) | |||||
{ | |||||
if (targetChannels > 0) | |||||
renderVoices (outputAudio, startSample, numSamples); | |||||
handleMidiEvent (m); | |||||
break; | |||||
} | |||||
if (samplesToNextMidiMessage < ((firstEvent && ! subBlockSubdivisionIsStrict) ? 1 : minimumSubBlockSize)) | |||||
{ | |||||
handleMidiEvent (m); | |||||
continue; | |||||
} | |||||
firstEvent = false; | |||||
if (targetChannels > 0) | |||||
renderVoices (outputAudio, startSample, samplesToNextMidiMessage); | |||||
handleMidiEvent (m); | |||||
startSample += samplesToNextMidiMessage; | |||||
numSamples -= samplesToNextMidiMessage; | |||||
} | |||||
while (midiIterator.getNextEvent (m, midiEventPos)) | |||||
handleMidiEvent (m); | |||||
} | |||||
// explicit template instantiation | |||||
template void Synthesiser::processNextBlock<float> (AudioBuffer<float>&, const MidiBuffer&, int, int); | |||||
template void Synthesiser::processNextBlock<double> (AudioBuffer<double>&, const MidiBuffer&, int, int); | |||||
void Synthesiser::renderVoices (AudioBuffer<float>& buffer, int startSample, int numSamples) | |||||
{ | |||||
for (auto* voice : voices) | |||||
voice->renderNextBlock (buffer, startSample, numSamples); | |||||
} | |||||
void Synthesiser::renderVoices (AudioBuffer<double>& buffer, int startSample, int numSamples) | |||||
{ | |||||
for (auto* voice : voices) | |||||
voice->renderNextBlock (buffer, startSample, numSamples); | |||||
} | |||||
void Synthesiser::handleMidiEvent (const MidiMessage& m) | |||||
{ | |||||
const int channel = m.getChannel(); | |||||
if (m.isNoteOn()) | |||||
{ | |||||
noteOn (channel, m.getNoteNumber(), m.getFloatVelocity()); | |||||
} | |||||
else if (m.isNoteOff()) | |||||
{ | |||||
noteOff (channel, m.getNoteNumber(), m.getFloatVelocity(), true); | |||||
} | |||||
else if (m.isAllNotesOff() || m.isAllSoundOff()) | |||||
{ | |||||
allNotesOff (channel, true); | |||||
} | |||||
else if (m.isPitchWheel()) | |||||
{ | |||||
const int wheelPos = m.getPitchWheelValue(); | |||||
lastPitchWheelValues [channel - 1] = wheelPos; | |||||
handlePitchWheel (channel, wheelPos); | |||||
} | |||||
else if (m.isAftertouch()) | |||||
{ | |||||
handleAftertouch (channel, m.getNoteNumber(), m.getAfterTouchValue()); | |||||
} | |||||
else if (m.isChannelPressure()) | |||||
{ | |||||
handleChannelPressure (channel, m.getChannelPressureValue()); | |||||
} | |||||
else if (m.isController()) | |||||
{ | |||||
handleController (channel, m.getControllerNumber(), m.getControllerValue()); | |||||
} | |||||
else if (m.isProgramChange()) | |||||
{ | |||||
handleProgramChange (channel, m.getProgramChangeNumber()); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void Synthesiser::noteOn (const int midiChannel, | |||||
const int midiNoteNumber, | |||||
const float velocity) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
for (auto* sound : sounds) | |||||
{ | |||||
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 (auto* voice : voices) | |||||
if (voice->getCurrentlyPlayingNote() == midiNoteNumber && voice->isPlayingChannel (midiChannel)) | |||||
stopVoice (voice, 1.0f, true); | |||||
startVoice (findFreeVoice (sound, midiChannel, midiNoteNumber, shouldStealNotes), | |||||
sound, midiChannel, midiNoteNumber, velocity); | |||||
} | |||||
} | |||||
} | |||||
void Synthesiser::startVoice (SynthesiserVoice* const voice, | |||||
SynthesiserSound* const sound, | |||||
const int midiChannel, | |||||
const int midiNoteNumber, | |||||
const float velocity) | |||||
{ | |||||
if (voice != nullptr && sound != nullptr) | |||||
{ | |||||
if (voice->currentlyPlayingSound != nullptr) | |||||
voice->stopNote (0.0f, false); | |||||
voice->currentlyPlayingNote = midiNoteNumber; | |||||
voice->currentPlayingMidiChannel = midiChannel; | |||||
voice->noteOnTime = ++lastNoteOnCounter; | |||||
voice->currentlyPlayingSound = sound; | |||||
voice->setKeyDown (true); | |||||
voice->setSostenutoPedalDown (false); | |||||
voice->setSustainPedalDown (sustainPedalsDown[midiChannel]); | |||||
voice->startNote (midiNoteNumber, velocity, sound, | |||||
lastPitchWheelValues [midiChannel - 1]); | |||||
} | |||||
} | |||||
void Synthesiser::stopVoice (SynthesiserVoice* voice, float velocity, const bool allowTailOff) | |||||
{ | |||||
jassert (voice != nullptr); | |||||
voice->stopNote (velocity, allowTailOff); | |||||
// the subclass MUST call clearCurrentNote() if it's not tailing off! RTFM for stopNote()! | |||||
jassert (allowTailOff || (voice->getCurrentlyPlayingNote() < 0 && voice->getCurrentlyPlayingSound() == 0)); | |||||
} | |||||
void Synthesiser::noteOff (const int midiChannel, | |||||
const int midiNoteNumber, | |||||
const float velocity, | |||||
const bool allowTailOff) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
for (auto* voice : voices) | |||||
{ | |||||
if (voice->getCurrentlyPlayingNote() == midiNoteNumber | |||||
&& voice->isPlayingChannel (midiChannel)) | |||||
{ | |||||
if (SynthesiserSound* const sound = voice->getCurrentlyPlayingSound()) | |||||
{ | |||||
if (sound->appliesToNote (midiNoteNumber) | |||||
&& sound->appliesToChannel (midiChannel)) | |||||
{ | |||||
jassert (! voice->keyIsDown || voice->isSustainPedalDown() == sustainPedalsDown [midiChannel]); | |||||
voice->setKeyDown (false); | |||||
if (! (voice->isSustainPedalDown() || voice->isSostenutoPedalDown())) | |||||
stopVoice (voice, velocity, allowTailOff); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
void Synthesiser::allNotesOff (const int midiChannel, const bool allowTailOff) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
for (auto* voice : voices) | |||||
if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | |||||
voice->stopNote (1.0f, allowTailOff); | |||||
sustainPedalsDown.clear(); | |||||
} | |||||
void Synthesiser::handlePitchWheel (const int midiChannel, const int wheelValue) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
for (auto* voice : voices) | |||||
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 (auto* voice : voices) | |||||
if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | |||||
voice->controllerMoved (controllerNumber, controllerValue); | |||||
} | |||||
void Synthesiser::handleAftertouch (int midiChannel, int midiNoteNumber, int aftertouchValue) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
for (auto* voice : voices) | |||||
if (voice->getCurrentlyPlayingNote() == midiNoteNumber | |||||
&& (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))) | |||||
voice->aftertouchChanged (aftertouchValue); | |||||
} | |||||
void Synthesiser::handleChannelPressure (int midiChannel, int channelPressureValue) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
for (auto* voice : voices) | |||||
if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | |||||
voice->channelPressureChanged (channelPressureValue); | |||||
} | |||||
void Synthesiser::handleSustainPedal (int midiChannel, bool isDown) | |||||
{ | |||||
jassert (midiChannel > 0 && midiChannel <= 16); | |||||
const ScopedLock sl (lock); | |||||
if (isDown) | |||||
{ | |||||
sustainPedalsDown.setBit (midiChannel); | |||||
for (auto* voice : voices) | |||||
if (voice->isPlayingChannel (midiChannel) && voice->isKeyDown()) | |||||
voice->setSustainPedalDown (true); | |||||
} | |||||
else | |||||
{ | |||||
for (auto* voice : voices) | |||||
{ | |||||
if (voice->isPlayingChannel (midiChannel)) | |||||
{ | |||||
voice->setSustainPedalDown (false); | |||||
if (! (voice->isKeyDown() || voice->isSostenutoPedalDown())) | |||||
stopVoice (voice, 1.0f, true); | |||||
} | |||||
} | |||||
sustainPedalsDown.clearBit (midiChannel); | |||||
} | |||||
} | |||||
void Synthesiser::handleSostenutoPedal (int midiChannel, bool isDown) | |||||
{ | |||||
jassert (midiChannel > 0 && midiChannel <= 16); | |||||
const ScopedLock sl (lock); | |||||
for (auto* voice : voices) | |||||
{ | |||||
if (voice->isPlayingChannel (midiChannel)) | |||||
{ | |||||
if (isDown) | |||||
voice->setSostenutoPedalDown (true); | |||||
else if (voice->isSostenutoPedalDown()) | |||||
stopVoice (voice, 1.0f, true); | |||||
} | |||||
} | |||||
} | |||||
void Synthesiser::handleSoftPedal (int midiChannel, bool /*isDown*/) | |||||
{ | |||||
ignoreUnused (midiChannel); | |||||
jassert (midiChannel > 0 && midiChannel <= 16); | |||||
} | |||||
void Synthesiser::handleProgramChange (int midiChannel, int programNumber) | |||||
{ | |||||
ignoreUnused (midiChannel, programNumber); | |||||
jassert (midiChannel > 0 && midiChannel <= 16); | |||||
} | |||||
//============================================================================== | |||||
SynthesiserVoice* Synthesiser::findFreeVoice (SynthesiserSound* soundToPlay, | |||||
int midiChannel, int midiNoteNumber, | |||||
const bool stealIfNoneAvailable) const | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
for (auto* voice : voices) | |||||
if ((! voice->isVoiceActive()) && voice->canPlaySound (soundToPlay)) | |||||
return voice; | |||||
if (stealIfNoneAvailable) | |||||
return findVoiceToSteal (soundToPlay, midiChannel, midiNoteNumber); | |||||
return nullptr; | |||||
} | |||||
struct VoiceAgeSorter | |||||
{ | |||||
static int compareElements (SynthesiserVoice* v1, SynthesiserVoice* v2) noexcept | |||||
{ | |||||
return v1->wasStartedBefore (*v2) ? -1 : (v2->wasStartedBefore (*v1) ? 1 : 0); | |||||
} | |||||
}; | |||||
SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, | |||||
int /*midiChannel*/, int midiNoteNumber) const | |||||
{ | |||||
// This voice-stealing algorithm applies the following heuristics: | |||||
// - Re-use the oldest notes first | |||||
// - Protect the lowest & topmost notes, even if sustained, but not if they've been released. | |||||
// apparently you are trying to render audio without having any voices... | |||||
jassert (! voices.isEmpty()); | |||||
// These are the voices we want to protect (ie: only steal if unavoidable) | |||||
SynthesiserVoice* low = nullptr; // Lowest sounding note, might be sustained, but NOT in release phase | |||||
SynthesiserVoice* top = nullptr; // Highest sounding note, might be sustained, but NOT in release phase | |||||
// this is a list of voices we can steal, sorted by how long they've been running | |||||
Array<SynthesiserVoice*> usableVoices; | |||||
usableVoices.ensureStorageAllocated (voices.size()); | |||||
for (auto* voice : voices) | |||||
{ | |||||
if (voice->canPlaySound (soundToPlay)) | |||||
{ | |||||
jassert (voice->isVoiceActive()); // We wouldn't be here otherwise | |||||
VoiceAgeSorter sorter; | |||||
usableVoices.addSorted (sorter, voice); | |||||
if (! voice->isPlayingButReleased()) // Don't protect released notes | |||||
{ | |||||
auto note = voice->getCurrentlyPlayingNote(); | |||||
if (low == nullptr || note < low->getCurrentlyPlayingNote()) | |||||
low = voice; | |||||
if (top == nullptr || note > top->getCurrentlyPlayingNote()) | |||||
top = voice; | |||||
} | |||||
} | |||||
} | |||||
// Eliminate pathological cases (ie: only 1 note playing): we always give precedence to the lowest note(s) | |||||
if (top == low) | |||||
top = nullptr; | |||||
// The oldest note that's playing with the target pitch is ideal.. | |||||
for (auto* voice : usableVoices) | |||||
if (voice->getCurrentlyPlayingNote() == midiNoteNumber) | |||||
return voice; | |||||
// Oldest voice that has been released (no finger on it and not held by sustain pedal) | |||||
for (auto* voice : usableVoices) | |||||
if (voice != low && voice != top && voice->isPlayingButReleased()) | |||||
return voice; | |||||
// Oldest voice that doesn't have a finger on it: | |||||
for (auto* voice : usableVoices) | |||||
if (voice != low && voice != top && ! voice->isKeyDown()) | |||||
return voice; | |||||
// Oldest voice that isn't protected | |||||
for (auto* voice : usableVoices) | |||||
if (voice != low && voice != top) | |||||
return voice; | |||||
// We've only got "protected" voices now: lowest note takes priority | |||||
jassert (low != nullptr); | |||||
// Duophonic synth: give priority to the bass note: | |||||
if (top != nullptr) | |||||
return top; | |||||
return low; | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,649 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Describes one of the sounds that a Synthesiser can play. | |||||
A synthesiser can contain one or more sounds, and a sound can choose which | |||||
midi notes and channels can trigger it. | |||||
The SynthesiserSound is a passive class that just describes what the sound is - | |||||
the actual audio rendering for a sound is done by a SynthesiserVoice. This allows | |||||
more than one SynthesiserVoice to play the same sound at the same time. | |||||
@see Synthesiser, SynthesiserVoice | |||||
*/ | |||||
class JUCE_API SynthesiserSound : public ReferenceCountedObject | |||||
{ | |||||
protected: | |||||
//============================================================================== | |||||
SynthesiserSound(); | |||||
public: | |||||
/** Destructor. */ | |||||
virtual ~SynthesiserSound(); | |||||
//============================================================================== | |||||
/** Returns true if this sound should be played when a given midi note is pressed. | |||||
The Synthesiser will use this information when deciding which sounds to trigger | |||||
for a given note. | |||||
*/ | |||||
virtual bool appliesToNote (int midiNoteNumber) = 0; | |||||
/** Returns true if the sound should be triggered by midi events on a given channel. | |||||
The Synthesiser will use this information when deciding which sounds to trigger | |||||
for a given note. | |||||
*/ | |||||
virtual bool appliesToChannel (int midiChannel) = 0; | |||||
/** The class is reference-counted, so this is a handy pointer class for it. */ | |||||
typedef ReferenceCountedObjectPtr<SynthesiserSound> Ptr; | |||||
private: | |||||
//============================================================================== | |||||
JUCE_LEAK_DETECTOR (SynthesiserSound) | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
Represents a voice that a Synthesiser can use to play a SynthesiserSound. | |||||
A voice plays a single sound at a time, and a synthesiser holds an array of | |||||
voices so that it can play polyphonically. | |||||
@see Synthesiser, SynthesiserSound | |||||
*/ | |||||
class JUCE_API SynthesiserVoice | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a voice. */ | |||||
SynthesiserVoice(); | |||||
/** Destructor. */ | |||||
virtual ~SynthesiserVoice(); | |||||
//============================================================================== | |||||
/** Returns the midi note that this voice is currently playing. | |||||
Returns a value less than 0 if no note is playing. | |||||
*/ | |||||
int getCurrentlyPlayingNote() const noexcept { return currentlyPlayingNote; } | |||||
/** Returns the sound that this voice is currently playing. | |||||
Returns nullptr if it's not playing. | |||||
*/ | |||||
SynthesiserSound::Ptr getCurrentlyPlayingSound() const noexcept { return currentlyPlayingSound; } | |||||
/** Must return true if this voice object is capable of playing the given sound. | |||||
If there are different classes of sound, and different classes of voice, a voice can | |||||
choose which ones it wants to take on. | |||||
A typical implementation of this method may just return true if there's only one type | |||||
of voice and sound, or it might check the type of the sound object passed-in and | |||||
see if it's one that it understands. | |||||
*/ | |||||
virtual bool canPlaySound (SynthesiserSound*) = 0; | |||||
/** Called to start a new note. | |||||
This will be called during the rendering callback, so must be fast and thread-safe. | |||||
*/ | |||||
virtual void startNote (int midiNoteNumber, | |||||
float velocity, | |||||
SynthesiserSound* sound, | |||||
int currentPitchWheelPosition) = 0; | |||||
/** Called to stop a note. | |||||
This will be called during the rendering callback, so must be fast and thread-safe. | |||||
The velocity indicates how quickly the note was released - 0 is slowly, 1 is quickly. | |||||
If allowTailOff is false or the voice doesn't want to tail-off, then it must stop all | |||||
sound immediately, and must call clearCurrentNote() to reset the state of this voice | |||||
and allow the synth to reassign it another sound. | |||||
If allowTailOff is true and the voice decides to do a tail-off, then it's allowed to | |||||
begin fading out its sound, and it can stop playing until it's finished. As soon as it | |||||
finishes playing (during the rendering callback), it must make sure that it calls | |||||
clearCurrentNote(). | |||||
*/ | |||||
virtual void stopNote (float velocity, bool allowTailOff) = 0; | |||||
/** Returns true if this voice is currently busy playing a sound. | |||||
By default this just checks the getCurrentlyPlayingNote() value, but can | |||||
be overridden for more advanced checking. | |||||
*/ | |||||
virtual bool isVoiceActive() const; | |||||
/** Called to let the voice know that the pitch wheel has been moved. | |||||
This will be called during the rendering callback, so must be fast and thread-safe. | |||||
*/ | |||||
virtual void pitchWheelMoved (int newPitchWheelValue) = 0; | |||||
/** Called to let the voice know that a midi controller has been moved. | |||||
This will be called during the rendering callback, so must be fast and thread-safe. | |||||
*/ | |||||
virtual void controllerMoved (int controllerNumber, int newControllerValue) = 0; | |||||
/** Called to let the voice know that the aftertouch has changed. | |||||
This will be called during the rendering callback, so must be fast and thread-safe. | |||||
*/ | |||||
virtual void aftertouchChanged (int newAftertouchValue); | |||||
/** Called to let the voice know that the channel pressure has changed. | |||||
This will be called during the rendering callback, so must be fast and thread-safe. | |||||
*/ | |||||
virtual void channelPressureChanged (int newChannelPressureValue); | |||||
//============================================================================== | |||||
/** 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 (AudioBuffer<float>& outputBuffer, | |||||
int startSample, | |||||
int numSamples) = 0; | |||||
/** A double-precision version of renderNextBlock() */ | |||||
virtual void renderNextBlock (AudioBuffer<double>& outputBuffer, | |||||
int startSample, | |||||
int numSamples); | |||||
/** Changes the voice's reference sample rate. | |||||
The rate is set so that subclasses know the output rate and can set their pitch | |||||
accordingly. | |||||
This method is called by the synth, and subclasses can access the current rate with | |||||
the currentSampleRate member. | |||||
*/ | |||||
virtual void setCurrentPlaybackSampleRate (double newRate); | |||||
/** Returns 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. | |||||
*/ | |||||
virtual bool isPlayingChannel (int midiChannel) const; | |||||
/** Returns the current target sample rate at which rendering is being done. | |||||
Subclasses may need to know this so that they can pitch things correctly. | |||||
*/ | |||||
double getSampleRate() const noexcept { return currentSampleRate; } | |||||
/** Returns true if the key that triggered this voice is still held down. | |||||
Note that the voice may still be playing after the key was released (e.g because the | |||||
sostenuto pedal is down). | |||||
*/ | |||||
bool isKeyDown() const noexcept { return keyIsDown; } | |||||
/** Allows you to modify the flag indicating that the key that triggered this voice is still held down. | |||||
@see isKeyDown | |||||
*/ | |||||
void setKeyDown (bool isNowDown) noexcept { keyIsDown = isNowDown; } | |||||
/** Returns true if the sustain pedal is currently active for this voice. */ | |||||
bool isSustainPedalDown() const noexcept { return sustainPedalDown; } | |||||
/** Modifies the sustain pedal flag. */ | |||||
void setSustainPedalDown (bool isNowDown) noexcept { sustainPedalDown = isNowDown; } | |||||
/** Returns true if the sostenuto pedal is currently active for this voice. */ | |||||
bool isSostenutoPedalDown() const noexcept { return sostenutoPedalDown; } | |||||
/** Modifies the sostenuto pedal flag. */ | |||||
void setSostenutoPedalDown (bool isNowDown) noexcept { sostenutoPedalDown = isNowDown; } | |||||
/** Returns true if a voice is sounding in its release phase **/ | |||||
bool isPlayingButReleased() const noexcept | |||||
{ | |||||
return isVoiceActive() && ! (isKeyDown() || isSostenutoPedalDown() || isSustainPedalDown()); | |||||
} | |||||
/** Returns true if this voice started playing its current note before the other voice did. */ | |||||
bool wasStartedBefore (const SynthesiserVoice& other) const noexcept; | |||||
protected: | |||||
/** Resets the state of this voice after a sound has finished playing. | |||||
The subclass must call this when it finishes playing a note and becomes available | |||||
to play new ones. | |||||
It must either call it in the stopNote() method, or if the voice is tailing off, | |||||
then it should call it later during the renderNextBlock method, as soon as it | |||||
finishes its tail-off. | |||||
It can also be called at any time during the render callback if the sound happens | |||||
to have finished, e.g. if it's playing a sample and the sample finishes. | |||||
*/ | |||||
void clearCurrentNote(); | |||||
private: | |||||
//============================================================================== | |||||
friend class Synthesiser; | |||||
double currentSampleRate = 44100.0; | |||||
int currentlyPlayingNote = -1, currentPlayingMidiChannel = 0; | |||||
uint32 noteOnTime = 0; | |||||
SynthesiserSound::Ptr currentlyPlayingSound; | |||||
bool keyIsDown = false, sustainPedalDown = false, sostenutoPedalDown = false; | |||||
AudioBuffer<float> tempBuffer; | |||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE | |||||
// Note the new parameters for this method. | |||||
virtual int stopNote (bool) { return 0; } | |||||
#endif | |||||
JUCE_LEAK_DETECTOR (SynthesiserVoice) | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
Base class for a musical device that can play sounds. | |||||
To create a synthesiser, you'll need to create a subclass of SynthesiserSound | |||||
to describe each sound available to your synth, and a subclass of SynthesiserVoice | |||||
which can play back one of these sounds. | |||||
Then you can use the addVoice() and addSound() methods to give the synthesiser a | |||||
set of sounds, and a set of voices it can use to play them. If you only give it | |||||
one voice it will be monophonic - the more voices it has, the more polyphony it'll | |||||
have available. | |||||
Then repeatedly call the renderNextBlock() method to produce the audio. Any midi | |||||
events that go in will be scanned for note on/off messages, and these are used to | |||||
start and stop the voices playing the appropriate sounds. | |||||
While it's playing, you can also cause notes to be triggered by calling the noteOn(), | |||||
noteOff() and other controller methods. | |||||
Before rendering, be sure to call the setCurrentPlaybackSampleRate() to tell it | |||||
what the target playback rate is. This value is passed on to the voices so that | |||||
they can pitch their output correctly. | |||||
*/ | |||||
class JUCE_API Synthesiser | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a new synthesiser. | |||||
You'll need to add some sounds and voices before it'll make any sound. | |||||
*/ | |||||
Synthesiser(); | |||||
/** Destructor. */ | |||||
virtual ~Synthesiser(); | |||||
//============================================================================== | |||||
/** Deletes all voices. */ | |||||
void clearVoices(); | |||||
/** Returns the number of voices that have been added. */ | |||||
int getNumVoices() const noexcept { return voices.size(); } | |||||
/** Returns one of the voices that have been added. */ | |||||
SynthesiserVoice* getVoice (int index) const; | |||||
/** Adds a new voice to the synth. | |||||
All the voices should be the same class of object and are treated equally. | |||||
The object passed in will be managed by the synthesiser, which will delete | |||||
it later on when no longer needed. The caller should not retain a pointer to the | |||||
voice. | |||||
*/ | |||||
SynthesiserVoice* addVoice (SynthesiserVoice* newVoice); | |||||
/** Deletes one of the voices. */ | |||||
void removeVoice (int index); | |||||
//============================================================================== | |||||
/** Deletes all sounds. */ | |||||
void clearSounds(); | |||||
/** Returns the number of sounds that have been added to the synth. */ | |||||
int getNumSounds() const noexcept { return sounds.size(); } | |||||
/** Returns one of the sounds. */ | |||||
SynthesiserSound* getSound (int index) const noexcept { return sounds [index]; } | |||||
/** Adds a new sound to the synthesiser. | |||||
The object passed in is reference counted, so will be deleted when the | |||||
synthesiser and all voices are no longer using it. | |||||
*/ | |||||
SynthesiserSound* addSound (const SynthesiserSound::Ptr& newSound); | |||||
/** Removes and deletes one of the sounds. */ | |||||
void removeSound (int index); | |||||
//============================================================================== | |||||
/** If set to true, then the synth will try to take over an existing voice if | |||||
it runs out and needs to play another note. | |||||
The value of this boolean is passed into findFreeVoice(), so the result will | |||||
depend on the implementation of this method. | |||||
*/ | |||||
void setNoteStealingEnabled (bool shouldStealNotes); | |||||
/** Returns true if note-stealing is enabled. | |||||
@see setNoteStealingEnabled | |||||
*/ | |||||
bool isNoteStealingEnabled() const noexcept { return shouldStealNotes; } | |||||
//============================================================================== | |||||
/** Triggers a note-on event. | |||||
The default method here will find all the sounds that want to be triggered by | |||||
this note/channel. For each sound, it'll try to find a free voice, and use the | |||||
voice to start playing the sound. | |||||
Subclasses might want to override this if they need a more complex algorithm. | |||||
This method will be called automatically according to the midi data passed into | |||||
renderNextBlock(), but may be called explicitly too. | |||||
The midiChannel parameter is the channel, between 1 and 16 inclusive. | |||||
*/ | |||||
virtual void noteOn (int midiChannel, | |||||
int midiNoteNumber, | |||||
float velocity); | |||||
/** Triggers a note-off event. | |||||
This will turn off any voices that are playing a sound for the given note/channel. | |||||
If allowTailOff is true, the voices will be allowed to fade out the notes gracefully | |||||
(if they can do). If this is false, the notes will all be cut off immediately. | |||||
This method will be called automatically according to the midi data passed into | |||||
renderNextBlock(), but may be called explicitly too. | |||||
The midiChannel parameter is the channel, between 1 and 16 inclusive. | |||||
*/ | |||||
virtual void noteOff (int midiChannel, | |||||
int midiNoteNumber, | |||||
float velocity, | |||||
bool allowTailOff); | |||||
/** Turns off all notes. | |||||
This will turn off any voices that are playing a sound on the given midi channel. | |||||
If midiChannel is 0 or less, then all voices will be turned off, regardless of | |||||
which channel they're playing. Otherwise it represents a valid midi channel, from | |||||
1 to 16 inclusive. | |||||
If allowTailOff is true, the voices will be allowed to fade out the notes gracefully | |||||
(if they can do). If this is false, the notes will all be cut off immediately. | |||||
This method will be called automatically according to the midi data passed into | |||||
renderNextBlock(), but may be called explicitly too. | |||||
*/ | |||||
virtual void allNotesOff (int midiChannel, | |||||
bool allowTailOff); | |||||
/** Sends a pitch-wheel message to any active voices. | |||||
This will send a pitch-wheel message to any voices that are playing sounds on | |||||
the given midi channel. | |||||
This method will be called automatically according to the midi data passed into | |||||
renderNextBlock(), but may be called explicitly too. | |||||
@param midiChannel the midi channel, from 1 to 16 inclusive | |||||
@param wheelValue the wheel position, from 0 to 0x3fff, as returned by MidiMessage::getPitchWheelValue() | |||||
*/ | |||||
virtual void handlePitchWheel (int midiChannel, | |||||
int wheelValue); | |||||
/** Sends a midi controller message to any active voices. | |||||
This will send a midi controller message to any voices that are playing sounds on | |||||
the given midi channel. | |||||
This method will be called automatically according to the midi data passed into | |||||
renderNextBlock(), but may be called explicitly too. | |||||
@param midiChannel the midi channel, from 1 to 16 inclusive | |||||
@param controllerNumber the midi controller type, as returned by MidiMessage::getControllerNumber() | |||||
@param controllerValue the midi controller value, between 0 and 127, as returned by MidiMessage::getControllerValue() | |||||
*/ | |||||
virtual void handleController (int midiChannel, | |||||
int controllerNumber, | |||||
int controllerValue); | |||||
/** Sends an aftertouch message. | |||||
This will send an aftertouch message to any voices that are playing sounds on | |||||
the given midi channel and note number. | |||||
This method will be called automatically according to the midi data passed into | |||||
renderNextBlock(), but may be called explicitly too. | |||||
@param midiChannel the midi channel, from 1 to 16 inclusive | |||||
@param midiNoteNumber the midi note number, 0 to 127 | |||||
@param aftertouchValue the aftertouch value, between 0 and 127, | |||||
as returned by MidiMessage::getAftertouchValue() | |||||
*/ | |||||
virtual void handleAftertouch (int midiChannel, int midiNoteNumber, int aftertouchValue); | |||||
/** Sends a channel pressure message. | |||||
This will send a channel pressure 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 channelPressureValue the pressure value, between 0 and 127, as returned | |||||
by MidiMessage::getChannelPressureValue() | |||||
*/ | |||||
virtual void handleChannelPressure (int midiChannel, int channelPressureValue); | |||||
/** Handles a sustain pedal event. */ | |||||
virtual void handleSustainPedal (int midiChannel, bool isDown); | |||||
/** Handles a sostenuto pedal event. */ | |||||
virtual void handleSostenutoPedal (int midiChannel, bool isDown); | |||||
/** Can be overridden to handle soft pedal events. */ | |||||
virtual void handleSoftPedal (int midiChannel, bool isDown); | |||||
/** Can be overridden to handle an incoming program change message. | |||||
The base class implementation of this has no effect, but you may want to make your | |||||
own synth react to program changes. | |||||
*/ | |||||
virtual void handleProgramChange (int midiChannel, | |||||
int programNumber); | |||||
//============================================================================== | |||||
/** Tells the synthesiser what the sample rate is for the audio it's being used to render. | |||||
This value is propagated to the voices so that they can use it to render the correct | |||||
pitches. | |||||
*/ | |||||
virtual void setCurrentPlaybackSampleRate (double sampleRate); | |||||
/** Creates the next block of audio output. | |||||
This will process the next numSamples of data from all the voices, and add that output | |||||
to the audio block supplied, starting from the offset specified. Note that the | |||||
data will be added to the current contents of the buffer, so you should clear it | |||||
before calling this method if necessary. | |||||
The midi events in the inputMidi buffer are parsed for note and controller events, | |||||
and these are used to trigger the voices. Note that the startSample offset applies | |||||
both to the audio output buffer and the midi input buffer, so any midi events | |||||
with timestamps outside the specified region will be ignored. | |||||
*/ | |||||
inline void renderNextBlock (AudioBuffer<float>& outputAudio, | |||||
const MidiBuffer& inputMidi, | |||||
int startSample, | |||||
int numSamples) | |||||
{ processNextBlock (outputAudio, inputMidi, startSample, numSamples); } | |||||
inline void renderNextBlock (AudioBuffer<double>& outputAudio, | |||||
const MidiBuffer& inputMidi, | |||||
int startSample, | |||||
int numSamples) | |||||
{ processNextBlock (outputAudio, inputMidi, startSample, numSamples); } | |||||
/** Returns the current target sample rate at which rendering is being done. | |||||
Subclasses may need to know this so that they can pitch things correctly. | |||||
*/ | |||||
double getSampleRate() const noexcept { return sampleRate; } | |||||
/** Sets a minimum limit on the size to which audio sub-blocks will be divided when rendering. | |||||
When rendering, the audio blocks that are passed into renderNextBlock() will be split up | |||||
into smaller blocks that lie between all the incoming midi messages, and it is these smaller | |||||
sub-blocks that are rendered with multiple calls to renderVoices(). | |||||
Obviously in a pathological case where there are midi messages on every sample, then | |||||
renderVoices() could be called once per sample and lead to poor performance, so this | |||||
setting allows you to set a lower limit on the block size. | |||||
The default setting is 32, which means that midi messages are accurate to about < 1ms | |||||
accuracy, which is probably fine for most purposes, but you may want to increase or | |||||
decrease this value for your synth. | |||||
If shouldBeStrict is true, the audio sub-blocks will strictly never be smaller than numSamples. | |||||
If shouldBeStrict is false (default), the first audio sub-block in the buffer is allowed | |||||
to be smaller, to make sure that the first MIDI event in a buffer will always be sample-accurate | |||||
(this can sometimes help to avoid quantisation or phasing issues). | |||||
*/ | |||||
void setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict = false) noexcept; | |||||
protected: | |||||
//============================================================================== | |||||
/** This is used to control access to the rendering callback and the note trigger methods. */ | |||||
CriticalSection lock; | |||||
OwnedArray<SynthesiserVoice> voices; | |||||
ReferenceCountedArray<SynthesiserSound> sounds; | |||||
/** The last pitch-wheel values for each midi channel. */ | |||||
int lastPitchWheelValues [16]; | |||||
/** Renders the voices for the given range. | |||||
By default this just calls renderNextBlock() on each voice, but you may need | |||||
to override it to handle custom cases. | |||||
*/ | |||||
virtual void renderVoices (AudioBuffer<float>& outputAudio, | |||||
int startSample, int numSamples); | |||||
virtual void renderVoices (AudioBuffer<double>& outputAudio, | |||||
int startSample, int numSamples); | |||||
/** Searches through the voices to find one that's not currently playing, and | |||||
which can play the given sound. | |||||
Returns nullptr if all voices are busy and stealing isn't enabled. | |||||
To implement a custom note-stealing algorithm, you can either override this | |||||
method, or (preferably) override findVoiceToSteal(). | |||||
*/ | |||||
virtual SynthesiserVoice* findFreeVoice (SynthesiserSound* soundToPlay, | |||||
int midiChannel, | |||||
int midiNoteNumber, | |||||
bool stealIfNoneAvailable) const; | |||||
/** Chooses a voice that is most suitable for being re-used. | |||||
The default method will attempt to find the oldest voice that isn't the | |||||
bottom or top note being played. If that's not suitable for your synth, | |||||
you can override this method and do something more cunning instead. | |||||
*/ | |||||
virtual SynthesiserVoice* findVoiceToSteal (SynthesiserSound* soundToPlay, | |||||
int midiChannel, | |||||
int midiNoteNumber) const; | |||||
/** Starts a specified voice playing a particular sound. | |||||
You'll probably never need to call this, it's used internally by noteOn(), but | |||||
may be needed by subclasses for custom behaviours. | |||||
*/ | |||||
void startVoice (SynthesiserVoice* voice, | |||||
SynthesiserSound* sound, | |||||
int midiChannel, | |||||
int midiNoteNumber, | |||||
float velocity); | |||||
/** Stops a given voice. | |||||
You should never need to call this, it's used internally by noteOff, but is protected | |||||
in case it's useful for some custom subclasses. It basically just calls through to | |||||
SynthesiserVoice::stopNote(), and has some assertions to sanity-check a few things. | |||||
*/ | |||||
void stopVoice (SynthesiserVoice*, float velocity, bool allowTailOff); | |||||
/** Can be overridden to do custom handling of incoming midi events. */ | |||||
virtual void handleMidiEvent (const MidiMessage&); | |||||
private: | |||||
//============================================================================== | |||||
template <typename floatType> | |||||
void processNextBlock (AudioBuffer<floatType>& outputAudio, | |||||
const MidiBuffer& inputMidi, | |||||
int startSample, | |||||
int numSamples); | |||||
//============================================================================== | |||||
double sampleRate = 0; | |||||
uint32 lastNoteOnCounter = 0; | |||||
int minimumSubBlockSize = 32; | |||||
bool subBlockSubdivisionIsStrict = false; | |||||
bool shouldStealNotes = true; | |||||
BigInteger sustainPedalsDown; | |||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE | |||||
// Note the new parameters for these methods. | |||||
virtual int findFreeVoice (const bool) const { return 0; } | |||||
virtual int noteOff (int, int, int) { return 0; } | |||||
virtual int findFreeVoice (SynthesiserSound*, const bool) { return 0; } | |||||
virtual int findVoiceToSteal (SynthesiserSound*) const { return 0; } | |||||
#endif | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Synthesiser) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,123 @@ | |||||
#!/usr/bin/make -f | |||||
# Makefile for juce_core # | |||||
# ---------------------- # | |||||
# Created by falkTX | |||||
# | |||||
CWD=../.. | |||||
MODULENAME=juce_core | |||||
include ../Makefile.mk | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
BUILD_CXX_FLAGS += $(JUCE_CORE_FLAGS) -I.. | |||||
ifeq ($(WIN32),true) | |||||
BUILD_CXX_FLAGS += -w | |||||
endif | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
ifeq ($(MACOS),true) | |||||
OBJS = $(OBJDIR)/$(MODULENAME).mm.o | |||||
OBJS_posix32 = $(OBJDIR)/$(MODULENAME).mm.posix32.o | |||||
OBJS_posix64 = $(OBJDIR)/$(MODULENAME).mm.posix64.o | |||||
else | |||||
OBJS = $(OBJDIR)/$(MODULENAME).cpp.o | |||||
OBJS_posix32 = $(OBJDIR)/$(MODULENAME).cpp.posix32.o | |||||
OBJS_posix64 = $(OBJDIR)/$(MODULENAME).cpp.posix64.o | |||||
endif | |||||
OBJS_win32 = $(OBJDIR)/$(MODULENAME).cpp.win32.o | |||||
OBJS_win64 = $(OBJDIR)/$(MODULENAME).cpp.win64.o | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
all: $(MODULEDIR)/$(MODULENAME).a | |||||
posix32: $(MODULEDIR)/$(MODULENAME).posix32.a | |||||
posix64: $(MODULEDIR)/$(MODULENAME).posix64.a | |||||
win32: $(MODULEDIR)/$(MODULENAME).win32.a | |||||
win64: $(MODULEDIR)/$(MODULENAME).win64.a | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
clean: | |||||
rm -f $(OBJDIR)/*.o $(MODULEDIR)/$(MODULENAME)*.a | |||||
debug: | |||||
$(MAKE) DEBUG=true | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
$(MODULEDIR)/$(MODULENAME).a: $(OBJS) | |||||
-@mkdir -p $(MODULEDIR) | |||||
@echo "Creating $(MODULENAME).a" | |||||
@rm -f $@ | |||||
@$(AR) crs $@ $^ | |||||
$(MODULEDIR)/$(MODULENAME).posix32.a: $(OBJS_posix32) | |||||
-@mkdir -p $(MODULEDIR) | |||||
@echo "Creating $(MODULENAME).posix32.a" | |||||
@rm -f $@ | |||||
@$(AR) crs $@ $^ | |||||
$(MODULEDIR)/$(MODULENAME).posix64.a: $(OBJS_posix64) | |||||
-@mkdir -p $(MODULEDIR) | |||||
@echo "Creating $(MODULENAME).posix64.a" | |||||
@rm -f $@ | |||||
@$(AR) crs $@ $^ | |||||
$(MODULEDIR)/$(MODULENAME).win32.a: $(OBJS_win32) | |||||
-@mkdir -p $(MODULEDIR) | |||||
@echo "Creating $(MODULENAME).win32.a" | |||||
@rm -f $@ | |||||
@$(AR) crs $@ $^ | |||||
$(MODULEDIR)/$(MODULENAME).win64.a: $(OBJS_win64) | |||||
-@mkdir -p $(MODULEDIR) | |||||
@echo "Creating $(MODULENAME).win64.a" | |||||
@rm -f $@ | |||||
@$(AR) crs $@ $^ | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
$(OBJDIR)/$(MODULENAME).cpp.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $<" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ | |||||
$(OBJDIR)/$(MODULENAME).cpp.%32.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $< (32bit)" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -c -o $@ | |||||
$(OBJDIR)/$(MODULENAME).cpp.%64.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $< (64bit)" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -c -o $@ | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
$(OBJDIR)/$(MODULENAME).mm.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $<" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) -ObjC++ -c -o $@ | |||||
$(OBJDIR)/$(MODULENAME).mm.%32.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $< (32bit)" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -ObjC++ -c -o $@ | |||||
$(OBJDIR)/$(MODULENAME).mm.%64.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $< (64bit)" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -ObjC++ -c -o $@ | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
-include $(OBJS:%.o=%.d) | |||||
-include $(OBJS_posix32:%.o=%.d) | |||||
-include $(OBJS_posix64:%.o=%.d) | |||||
-include $(OBJS_win32:%.o=%.d) | |||||
-include $(OBJS_win64:%.o=%.d) | |||||
# ---------------------------------------------------------------------------------------------------------------------------- |
@@ -0,0 +1,234 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
AbstractFifo::AbstractFifo (const int capacity) noexcept | |||||
: bufferSize (capacity) | |||||
{ | |||||
jassert (bufferSize > 0); | |||||
} | |||||
AbstractFifo::~AbstractFifo() {} | |||||
int AbstractFifo::getTotalSize() const noexcept { return bufferSize; } | |||||
int AbstractFifo::getFreeSpace() const noexcept { return bufferSize - getNumReady() - 1; } | |||||
int AbstractFifo::getNumReady() const noexcept | |||||
{ | |||||
const int vs = validStart.get(); | |||||
const int ve = validEnd.get(); | |||||
return ve >= vs ? (ve - vs) : (bufferSize - (vs - ve)); | |||||
} | |||||
void AbstractFifo::reset() noexcept | |||||
{ | |||||
validEnd = 0; | |||||
validStart = 0; | |||||
} | |||||
void AbstractFifo::setTotalSize (int newSize) noexcept | |||||
{ | |||||
jassert (newSize > 0); | |||||
reset(); | |||||
bufferSize = newSize; | |||||
} | |||||
//============================================================================== | |||||
void AbstractFifo::prepareToWrite (int numToWrite, int& startIndex1, int& blockSize1, int& startIndex2, int& blockSize2) const noexcept | |||||
{ | |||||
const int vs = validStart.get(); | |||||
const int ve = validEnd.value; | |||||
const int freeSpace = ve >= vs ? (bufferSize - (ve - vs)) : (vs - ve); | |||||
numToWrite = jmin (numToWrite, freeSpace - 1); | |||||
if (numToWrite <= 0) | |||||
{ | |||||
startIndex1 = 0; | |||||
startIndex2 = 0; | |||||
blockSize1 = 0; | |||||
blockSize2 = 0; | |||||
} | |||||
else | |||||
{ | |||||
startIndex1 = ve; | |||||
startIndex2 = 0; | |||||
blockSize1 = jmin (bufferSize - ve, numToWrite); | |||||
numToWrite -= blockSize1; | |||||
blockSize2 = numToWrite <= 0 ? 0 : jmin (numToWrite, vs); | |||||
} | |||||
} | |||||
void AbstractFifo::finishedWrite (int numWritten) noexcept | |||||
{ | |||||
jassert (numWritten >= 0 && numWritten < bufferSize); | |||||
int newEnd = validEnd.value + numWritten; | |||||
if (newEnd >= bufferSize) | |||||
newEnd -= bufferSize; | |||||
validEnd = newEnd; | |||||
} | |||||
void AbstractFifo::prepareToRead (int numWanted, int& startIndex1, int& blockSize1, int& startIndex2, int& blockSize2) const noexcept | |||||
{ | |||||
const int vs = validStart.value; | |||||
const int ve = validEnd.get(); | |||||
const int numReady = ve >= vs ? (ve - vs) : (bufferSize - (vs - ve)); | |||||
numWanted = jmin (numWanted, numReady); | |||||
if (numWanted <= 0) | |||||
{ | |||||
startIndex1 = 0; | |||||
startIndex2 = 0; | |||||
blockSize1 = 0; | |||||
blockSize2 = 0; | |||||
} | |||||
else | |||||
{ | |||||
startIndex1 = vs; | |||||
startIndex2 = 0; | |||||
blockSize1 = jmin (bufferSize - vs, numWanted); | |||||
numWanted -= blockSize1; | |||||
blockSize2 = numWanted <= 0 ? 0 : jmin (numWanted, ve); | |||||
} | |||||
} | |||||
void AbstractFifo::finishedRead (int numRead) noexcept | |||||
{ | |||||
jassert (numRead >= 0 && numRead <= bufferSize); | |||||
int newStart = validStart.value + numRead; | |||||
if (newStart >= bufferSize) | |||||
newStart -= bufferSize; | |||||
validStart = newStart; | |||||
} | |||||
//============================================================================== | |||||
//============================================================================== | |||||
#if JUCE_UNIT_TESTS | |||||
class AbstractFifoTests : public UnitTest | |||||
{ | |||||
public: | |||||
AbstractFifoTests() : UnitTest ("Abstract Fifo", "Containers") {} | |||||
class WriteThread : public Thread | |||||
{ | |||||
public: | |||||
WriteThread (AbstractFifo& f, int* b, Random rng) | |||||
: Thread ("fifo writer"), fifo (f), buffer (b), random (rng) | |||||
{ | |||||
startThread(); | |||||
} | |||||
~WriteThread() | |||||
{ | |||||
stopThread (5000); | |||||
} | |||||
void run() | |||||
{ | |||||
int n = 0; | |||||
while (! threadShouldExit()) | |||||
{ | |||||
int num = random.nextInt (2000) + 1; | |||||
int start1, size1, start2, size2; | |||||
fifo.prepareToWrite (num, start1, size1, start2, size2); | |||||
jassert (size1 >= 0 && size2 >= 0); | |||||
jassert (size1 == 0 || (start1 >= 0 && start1 < fifo.getTotalSize())); | |||||
jassert (size2 == 0 || (start2 >= 0 && start2 < fifo.getTotalSize())); | |||||
for (int i = 0; i < size1; ++i) | |||||
buffer [start1 + i] = n++; | |||||
for (int i = 0; i < size2; ++i) | |||||
buffer [start2 + i] = n++; | |||||
fifo.finishedWrite (size1 + size2); | |||||
} | |||||
} | |||||
private: | |||||
AbstractFifo& fifo; | |||||
int* buffer; | |||||
Random random; | |||||
}; | |||||
void runTest() override | |||||
{ | |||||
beginTest ("AbstractFifo"); | |||||
int buffer [5000]; | |||||
AbstractFifo fifo (numElementsInArray (buffer)); | |||||
WriteThread writer (fifo, buffer, getRandom()); | |||||
int n = 0; | |||||
Random r = getRandom(); | |||||
r.combineSeed (12345); | |||||
for (int count = 100000; --count >= 0;) | |||||
{ | |||||
int num = r.nextInt (6000) + 1; | |||||
int start1, size1, start2, size2; | |||||
fifo.prepareToRead (num, start1, size1, start2, size2); | |||||
if (! (size1 >= 0 && size2 >= 0) | |||||
&& (size1 == 0 || (start1 >= 0 && start1 < fifo.getTotalSize())) | |||||
&& (size2 == 0 || (start2 >= 0 && start2 < fifo.getTotalSize()))) | |||||
{ | |||||
expect (false, "prepareToRead returned -ve values"); | |||||
break; | |||||
} | |||||
bool failed = false; | |||||
for (int i = 0; i < size1; ++i) | |||||
failed = (buffer [start1 + i] != n++) || failed; | |||||
for (int i = 0; i < size2; ++i) | |||||
failed = (buffer [start2 + i] != n++) || failed; | |||||
if (failed) | |||||
{ | |||||
expect (false, "read values were incorrect"); | |||||
break; | |||||
} | |||||
fifo.finishedRead (size1 + size2); | |||||
} | |||||
} | |||||
}; | |||||
static AbstractFifoTests fifoUnitTests; | |||||
#endif | |||||
} // namespace juce |
@@ -0,0 +1,212 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Encapsulates the logic required to implement a lock-free FIFO. | |||||
This class handles the logic needed when building a single-reader, single-writer FIFO. | |||||
It doesn't actually hold any data itself, but your FIFO class can use one of these to manage | |||||
its position and status when reading or writing to it. | |||||
To use it, you can call prepareToWrite() to determine the position within your own buffer that | |||||
an incoming block of data should be stored, and prepareToRead() to find out when the next | |||||
outgoing block should be read from. | |||||
e.g. | |||||
@code | |||||
class MyFifo | |||||
{ | |||||
public: | |||||
MyFifo() : abstractFifo (1024) | |||||
{ | |||||
} | |||||
void addToFifo (const int* someData, int numItems) | |||||
{ | |||||
int start1, size1, start2, size2; | |||||
abstractFifo.prepareToWrite (numItems, start1, size1, start2, size2); | |||||
if (size1 > 0) | |||||
copySomeData (myBuffer + start1, someData, size1); | |||||
if (size2 > 0) | |||||
copySomeData (myBuffer + start2, someData + size1, size2); | |||||
abstractFifo.finishedWrite (size1 + size2); | |||||
} | |||||
void readFromFifo (int* someData, int numItems) | |||||
{ | |||||
int start1, size1, start2, size2; | |||||
abstractFifo.prepareToRead (numItems, start1, size1, start2, size2); | |||||
if (size1 > 0) | |||||
copySomeData (someData, myBuffer + start1, size1); | |||||
if (size2 > 0) | |||||
copySomeData (someData + size1, myBuffer + start2, size2); | |||||
abstractFifo.finishedRead (size1 + size2); | |||||
} | |||||
private: | |||||
AbstractFifo abstractFifo; | |||||
int myBuffer [1024]; | |||||
}; | |||||
@endcode | |||||
*/ | |||||
class JUCE_API AbstractFifo | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a FIFO to manage a buffer with the specified capacity. */ | |||||
AbstractFifo (int capacity) noexcept; | |||||
/** Destructor */ | |||||
~AbstractFifo(); | |||||
//============================================================================== | |||||
/** Returns the total size of the buffer being managed. */ | |||||
int getTotalSize() const noexcept; | |||||
/** Returns the number of items that can currently be added to the buffer without it overflowing. */ | |||||
int getFreeSpace() const noexcept; | |||||
/** Returns the number of items that can currently be read from the buffer. */ | |||||
int getNumReady() const noexcept; | |||||
/** Clears the buffer positions, so that it appears empty. */ | |||||
void reset() noexcept; | |||||
/** Changes the buffer's total size. | |||||
Note that this isn't thread-safe, so don't call it if there's any danger that it | |||||
might overlap with a call to any other method in this class! | |||||
*/ | |||||
void setTotalSize (int newSize) noexcept; | |||||
//============================================================================== | |||||
/** Returns the location within the buffer at which an incoming block of data should be written. | |||||
Because the section of data that you want to add to the buffer may overlap the end | |||||
and wrap around to the start, two blocks within your buffer are returned, and you | |||||
should copy your data into the first one, with any remaining data spilling over into | |||||
the second. | |||||
If the number of items you ask for is too large to fit within the buffer's free space, then | |||||
blockSize1 + blockSize2 may add up to a lower value than numToWrite. If this happens, you | |||||
may decide to keep waiting and re-trying the method until there's enough space available. | |||||
After calling this method, if you choose to write your data into the blocks returned, you | |||||
must call finishedWrite() to tell the FIFO how much data you actually added. | |||||
e.g. | |||||
@code | |||||
void addToFifo (const int* someData, int numItems) | |||||
{ | |||||
int start1, size1, start2, size2; | |||||
prepareToWrite (numItems, start1, size1, start2, size2); | |||||
if (size1 > 0) | |||||
copySomeData (myBuffer + start1, someData, size1); | |||||
if (size2 > 0) | |||||
copySomeData (myBuffer + start2, someData + size1, size2); | |||||
finishedWrite (size1 + size2); | |||||
} | |||||
@endcode | |||||
@param numToWrite indicates how many items you'd like to add to the buffer | |||||
@param startIndex1 on exit, this will contain the start index in your buffer at which your data should be written | |||||
@param blockSize1 on exit, this indicates how many items can be written to the block starting at startIndex1 | |||||
@param startIndex2 on exit, this will contain the start index in your buffer at which any data that didn't fit into | |||||
the first block should be written | |||||
@param blockSize2 on exit, this indicates how many items can be written to the block starting at startIndex2 | |||||
@see finishedWrite | |||||
*/ | |||||
void prepareToWrite (int numToWrite, int& startIndex1, int& blockSize1, int& startIndex2, int& blockSize2) const noexcept; | |||||
/** Called after writing from the FIFO, to indicate that this many items have been added. | |||||
@see prepareToWrite | |||||
*/ | |||||
void finishedWrite (int numWritten) noexcept; | |||||
/** Returns the location within the buffer from which the next block of data should be read. | |||||
Because the section of data that you want to read from the buffer may overlap the end | |||||
and wrap around to the start, two blocks within your buffer are returned, and you | |||||
should read from both of them. | |||||
If the number of items you ask for is greater than the amount of data available, then | |||||
blockSize1 + blockSize2 may add up to a lower value than numWanted. If this happens, you | |||||
may decide to keep waiting and re-trying the method until there's enough data available. | |||||
After calling this method, if you choose to read the data, you must call finishedRead() to | |||||
tell the FIFO how much data you have consumed. | |||||
e.g. | |||||
@code | |||||
void readFromFifo (int* someData, int numItems) | |||||
{ | |||||
int start1, size1, start2, size2; | |||||
prepareToRead (numSamples, start1, size1, start2, size2); | |||||
if (size1 > 0) | |||||
copySomeData (someData, myBuffer + start1, size1); | |||||
if (size2 > 0) | |||||
copySomeData (someData + size1, myBuffer + start2, size2); | |||||
finishedRead (size1 + size2); | |||||
} | |||||
@endcode | |||||
@param numWanted indicates how many items you'd like to add to the buffer | |||||
@param startIndex1 on exit, this will contain the start index in your buffer at which your data should be written | |||||
@param blockSize1 on exit, this indicates how many items can be written to the block starting at startIndex1 | |||||
@param startIndex2 on exit, this will contain the start index in your buffer at which any data that didn't fit into | |||||
the first block should be written | |||||
@param blockSize2 on exit, this indicates how many items can be written to the block starting at startIndex2 | |||||
@see finishedRead | |||||
*/ | |||||
void prepareToRead (int numWanted, int& startIndex1, int& blockSize1, int& startIndex2, int& blockSize2) const noexcept; | |||||
/** Called after reading from the FIFO, to indicate that this many items have now been consumed. | |||||
@see prepareToRead | |||||
*/ | |||||
void finishedRead (int numRead) noexcept; | |||||
private: | |||||
//============================================================================== | |||||
int bufferSize; | |||||
Atomic <int> validStart, validEnd; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AbstractFifo) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,128 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Implements some basic array storage allocation functions. | |||||
This class isn't really for public use - it's used by the other | |||||
array classes, but might come in handy for some purposes. | |||||
It inherits from a critical section class to allow the arrays to use | |||||
the "empty base class optimisation" pattern to reduce their footprint. | |||||
@see Array, OwnedArray, ReferenceCountedArray | |||||
*/ | |||||
template <class ElementType, class TypeOfCriticalSectionToUse> | |||||
class ArrayAllocationBase : public TypeOfCriticalSectionToUse | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an empty array. */ | |||||
ArrayAllocationBase() noexcept | |||||
: numAllocated (0) | |||||
{ | |||||
} | |||||
/** Destructor. */ | |||||
~ArrayAllocationBase() noexcept | |||||
{ | |||||
} | |||||
ArrayAllocationBase (ArrayAllocationBase<ElementType, TypeOfCriticalSectionToUse>&& other) noexcept | |||||
: elements (static_cast<HeapBlock<ElementType>&&> (other.elements)), | |||||
numAllocated (other.numAllocated) | |||||
{ | |||||
} | |||||
ArrayAllocationBase& operator= (ArrayAllocationBase<ElementType, TypeOfCriticalSectionToUse>&& other) noexcept | |||||
{ | |||||
elements = static_cast<HeapBlock<ElementType>&&> (other.elements); | |||||
numAllocated = other.numAllocated; | |||||
return *this; | |||||
} | |||||
//============================================================================== | |||||
/** Changes the amount of storage allocated. | |||||
This will retain any data currently held in the array, and either add or | |||||
remove extra space at the end. | |||||
@param numElements the number of elements that are needed | |||||
*/ | |||||
void setAllocatedSize (const int numElements) | |||||
{ | |||||
if (numAllocated != numElements) | |||||
{ | |||||
if (numElements > 0) | |||||
elements.realloc ((size_t) numElements); | |||||
else | |||||
elements.free(); | |||||
numAllocated = numElements; | |||||
} | |||||
} | |||||
/** Increases the amount of storage allocated if it is less than a given amount. | |||||
This will retain any data currently held in the array, but will add | |||||
extra space at the end to make sure there it's at least as big as the size | |||||
passed in. If it's already bigger, no action is taken. | |||||
@param minNumElements the minimum number of elements that are needed | |||||
*/ | |||||
void ensureAllocatedSize (const int minNumElements) | |||||
{ | |||||
if (minNumElements > numAllocated) | |||||
setAllocatedSize ((minNumElements + minNumElements / 2 + 8) & ~7); | |||||
jassert (numAllocated <= 0 || elements != nullptr); | |||||
} | |||||
/** Minimises the amount of storage allocated so that it's no more than | |||||
the given number of elements. | |||||
*/ | |||||
void shrinkToNoMoreThan (const int maxNumElements) | |||||
{ | |||||
if (maxNumElements < numAllocated) | |||||
setAllocatedSize (maxNumElements); | |||||
} | |||||
/** Swap the contents of two objects. */ | |||||
void swapWith (ArrayAllocationBase <ElementType, TypeOfCriticalSectionToUse>& other) noexcept | |||||
{ | |||||
elements.swapWith (other.elements); | |||||
std::swap (numAllocated, other.numAllocated); | |||||
} | |||||
//============================================================================== | |||||
HeapBlock<ElementType> elements; | |||||
int numAllocated; | |||||
private: | |||||
JUCE_DECLARE_NON_COPYABLE (ArrayAllocationBase) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,132 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
DynamicObject::DynamicObject() | |||||
{ | |||||
} | |||||
DynamicObject::DynamicObject (const DynamicObject& other) | |||||
: ReferenceCountedObject(), properties (other.properties) | |||||
{ | |||||
} | |||||
DynamicObject::~DynamicObject() | |||||
{ | |||||
} | |||||
bool DynamicObject::hasProperty (const Identifier& propertyName) const | |||||
{ | |||||
const var* const v = properties.getVarPointer (propertyName); | |||||
return v != nullptr && ! v->isMethod(); | |||||
} | |||||
const var& DynamicObject::getProperty (const Identifier& propertyName) const | |||||
{ | |||||
return properties [propertyName]; | |||||
} | |||||
void DynamicObject::setProperty (const Identifier& propertyName, const var& newValue) | |||||
{ | |||||
properties.set (propertyName, newValue); | |||||
} | |||||
void DynamicObject::removeProperty (const Identifier& propertyName) | |||||
{ | |||||
properties.remove (propertyName); | |||||
} | |||||
bool DynamicObject::hasMethod (const Identifier& methodName) const | |||||
{ | |||||
return getProperty (methodName).isMethod(); | |||||
} | |||||
var DynamicObject::invokeMethod (Identifier method, const var::NativeFunctionArgs& args) | |||||
{ | |||||
if (auto function = properties [method].getNativeFunction()) | |||||
return function (args); | |||||
return {}; | |||||
} | |||||
void DynamicObject::setMethod (Identifier name, var::NativeFunction function) | |||||
{ | |||||
properties.set (name, var (function)); | |||||
} | |||||
void DynamicObject::clear() | |||||
{ | |||||
properties.clear(); | |||||
} | |||||
void DynamicObject::cloneAllProperties() | |||||
{ | |||||
for (int i = properties.size(); --i >= 0;) | |||||
if (auto* v = properties.getVarPointerAt (i)) | |||||
*v = v->clone(); | |||||
} | |||||
DynamicObject::Ptr DynamicObject::clone() | |||||
{ | |||||
Ptr d (new DynamicObject (*this)); | |||||
d->cloneAllProperties(); | |||||
return d; | |||||
} | |||||
void DynamicObject::writeAsJSON (OutputStream& out, const int indentLevel, const bool allOnOneLine, int maximumDecimalPlaces) | |||||
{ | |||||
out << '{'; | |||||
if (! allOnOneLine) | |||||
out << newLine; | |||||
const int numValues = properties.size(); | |||||
for (int i = 0; i < numValues; ++i) | |||||
{ | |||||
if (! allOnOneLine) | |||||
JSONFormatter::writeSpaces (out, indentLevel + JSONFormatter::indentSize); | |||||
out << '"'; | |||||
JSONFormatter::writeString (out, properties.getName (i)); | |||||
out << "\": "; | |||||
JSONFormatter::write (out, properties.getValueAt (i), indentLevel + JSONFormatter::indentSize, allOnOneLine, maximumDecimalPlaces); | |||||
if (i < numValues - 1) | |||||
{ | |||||
if (allOnOneLine) | |||||
out << ", "; | |||||
else | |||||
out << ',' << newLine; | |||||
} | |||||
else if (! allOnOneLine) | |||||
out << newLine; | |||||
} | |||||
if (! allOnOneLine) | |||||
JSONFormatter::writeSpaces (out, indentLevel); | |||||
out << '}'; | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,130 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Represents a dynamically implemented object. | |||||
This class is primarily intended for wrapping scripting language objects, | |||||
but could be used for other purposes. | |||||
An instance of a DynamicObject can be used to store named properties, and | |||||
by subclassing hasMethod() and invokeMethod(), you can give your object | |||||
methods. | |||||
*/ | |||||
class JUCE_API DynamicObject : public ReferenceCountedObject | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
DynamicObject(); | |||||
DynamicObject (const DynamicObject&); | |||||
~DynamicObject(); | |||||
typedef ReferenceCountedObjectPtr<DynamicObject> Ptr; | |||||
//============================================================================== | |||||
/** Returns true if the object has a property with this name. | |||||
Note that if the property is actually a method, this will return false. | |||||
*/ | |||||
virtual bool hasProperty (const Identifier& propertyName) const; | |||||
/** Returns a named property. | |||||
This returns var() if no such property exists. | |||||
*/ | |||||
virtual const var& getProperty (const Identifier& propertyName) const; | |||||
/** Sets a named property. */ | |||||
virtual void setProperty (const Identifier& propertyName, const var& newValue); | |||||
/** Removes a named property. */ | |||||
virtual void removeProperty (const Identifier& propertyName); | |||||
//============================================================================== | |||||
/** Checks whether this object has the specified method. | |||||
The default implementation of this just checks whether there's a property | |||||
with this name that's actually a method, but this can be overridden for | |||||
building objects with dynamic invocation. | |||||
*/ | |||||
virtual bool hasMethod (const Identifier& methodName) const; | |||||
/** Invokes a named method on this object. | |||||
The default implementation looks up the named property, and if it's a method | |||||
call, then it invokes it. | |||||
This method is virtual to allow more dynamic invocation to used for objects | |||||
where the methods may not already be set as properies. | |||||
*/ | |||||
virtual var invokeMethod (Identifier methodName, | |||||
const var::NativeFunctionArgs& args); | |||||
/** Adds a method to the class. | |||||
This is basically the same as calling setProperty (methodName, (var::NativeFunction) myFunction), but | |||||
helps to avoid accidentally invoking the wrong type of var constructor. It also makes | |||||
the code easier to read, | |||||
*/ | |||||
void setMethod (Identifier methodName, var::NativeFunction function); | |||||
//============================================================================== | |||||
/** Removes all properties and methods from the object. */ | |||||
void clear(); | |||||
/** Returns the NamedValueSet that holds the object's properties. */ | |||||
NamedValueSet& getProperties() noexcept { return properties; } | |||||
/** Calls var::clone() on all the properties that this object contains. */ | |||||
void cloneAllProperties(); | |||||
//============================================================================== | |||||
/** Returns a clone of this object. | |||||
The default implementation of this method just returns a new DynamicObject | |||||
with a (deep) copy of all of its properties. Subclasses can override this to | |||||
implement their own custom copy routines. | |||||
*/ | |||||
virtual Ptr clone(); | |||||
//============================================================================== | |||||
/** Writes this object to a text stream in JSON format. | |||||
This method is used by JSON::toString and JSON::writeToStream, and you should | |||||
never need to call it directly, but it's virtual so that custom object types | |||||
can stringify themselves appropriately. | |||||
*/ | |||||
virtual void writeAsJSON (OutputStream&, int indentLevel, bool allOnOneLine, int maximumDecimalPlaces); | |||||
private: | |||||
//============================================================================== | |||||
NamedValueSet properties; | |||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE | |||||
// This method has been deprecated - use var::invoke instead | |||||
virtual void invokeMethod (const Identifier&, const var*, int) {} | |||||
#endif | |||||
JUCE_LEAK_DETECTOR (DynamicObject) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,188 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
#ifndef DOXYGEN | |||||
/** This is an internal helper class which converts a juce ElementComparator style | |||||
class (using a "compareElements" method) into a class that's compatible with | |||||
std::sort (i.e. using an operator() to compare the elements) | |||||
*/ | |||||
template <typename ElementComparator> | |||||
struct SortFunctionConverter | |||||
{ | |||||
SortFunctionConverter (ElementComparator& e) : comparator (e) {} | |||||
template <typename Type> | |||||
bool operator() (Type a, Type b) { return comparator.compareElements (a, b) < 0; } | |||||
private: | |||||
ElementComparator& comparator; | |||||
SortFunctionConverter& operator= (const SortFunctionConverter&) JUCE_DELETED_FUNCTION; | |||||
}; | |||||
#endif | |||||
//============================================================================== | |||||
/** | |||||
Sorts a range of elements in an array. | |||||
The comparator object that is passed-in must define a public method with the following | |||||
signature: | |||||
@code | |||||
int compareElements (ElementType first, ElementType second); | |||||
@endcode | |||||
..and this method must return: | |||||
- a value of < 0 if the first comes before the second | |||||
- a value of 0 if the two objects are equivalent | |||||
- a value of > 0 if the second comes before the first | |||||
To improve performance, the compareElements() method can be declared as static or const. | |||||
@param comparator an object which defines a compareElements() method | |||||
@param array the array to sort | |||||
@param firstElement the index of the first element of the range to be sorted | |||||
@param lastElement the index of the last element in the range that needs | |||||
sorting (this is inclusive) | |||||
@param retainOrderOfEquivalentItems if true, the order of items that the | |||||
comparator deems the same will be maintained - this will be | |||||
a slower algorithm than if they are allowed to be moved around. | |||||
@see sortArrayRetainingOrder | |||||
*/ | |||||
template <class ElementType, class ElementComparator> | |||||
static void sortArray (ElementComparator& comparator, | |||||
ElementType* const array, | |||||
int firstElement, | |||||
int lastElement, | |||||
const bool retainOrderOfEquivalentItems) | |||||
{ | |||||
SortFunctionConverter<ElementComparator> converter (comparator); | |||||
if (retainOrderOfEquivalentItems) | |||||
std::stable_sort (array + firstElement, array + lastElement + 1, converter); | |||||
else | |||||
std::sort (array + firstElement, array + lastElement + 1, converter); | |||||
} | |||||
//============================================================================== | |||||
/** | |||||
Searches a sorted array of elements, looking for the index at which a specified value | |||||
should be inserted for it to be in the correct order. | |||||
The comparator object that is passed-in must define a public method with the following | |||||
signature: | |||||
@code | |||||
int compareElements (ElementType first, ElementType second); | |||||
@endcode | |||||
..and this method must return: | |||||
- a value of < 0 if the first comes before the second | |||||
- a value of 0 if the two objects are equivalent | |||||
- a value of > 0 if the second comes before the first | |||||
To improve performance, the compareElements() method can be declared as static or const. | |||||
@param comparator an object which defines a compareElements() method | |||||
@param array the array to search | |||||
@param newElement the value that is going to be inserted | |||||
@param firstElement the index of the first element to search | |||||
@param lastElement the index of the last element in the range (this is non-inclusive) | |||||
*/ | |||||
template <class ElementType, class ElementComparator> | |||||
static int findInsertIndexInSortedArray (ElementComparator& comparator, | |||||
ElementType* const array, | |||||
const ElementType newElement, | |||||
int firstElement, | |||||
int lastElement) | |||||
{ | |||||
jassert (firstElement <= lastElement); | |||||
ignoreUnused (comparator); // if you pass in an object with a static compareElements() method, this | |||||
// avoids getting warning messages about the parameter being unused | |||||
while (firstElement < lastElement) | |||||
{ | |||||
if (comparator.compareElements (newElement, array [firstElement]) == 0) | |||||
{ | |||||
++firstElement; | |||||
break; | |||||
} | |||||
else | |||||
{ | |||||
const int halfway = (firstElement + lastElement) >> 1; | |||||
if (halfway == firstElement) | |||||
{ | |||||
if (comparator.compareElements (newElement, array [halfway]) >= 0) | |||||
++firstElement; | |||||
break; | |||||
} | |||||
else if (comparator.compareElements (newElement, array [halfway]) >= 0) | |||||
{ | |||||
firstElement = halfway; | |||||
} | |||||
else | |||||
{ | |||||
lastElement = halfway; | |||||
} | |||||
} | |||||
} | |||||
return firstElement; | |||||
} | |||||
//============================================================================== | |||||
/** | |||||
A simple ElementComparator class that can be used to sort an array of | |||||
objects that support the '<' operator. | |||||
This will work for primitive types and objects that implement operator<(). | |||||
Example: @code | |||||
Array <int> myArray; | |||||
DefaultElementComparator<int> sorter; | |||||
myArray.sort (sorter); | |||||
@endcode | |||||
@see ElementComparator | |||||
*/ | |||||
template <class ElementType> | |||||
class DefaultElementComparator | |||||
{ | |||||
private: | |||||
typedef typename TypeHelpers::ParameterType<ElementType>::type ParameterType; | |||||
public: | |||||
static int compareElements (ParameterType first, ParameterType second) | |||||
{ | |||||
return (first < second) ? -1 : ((second < first) ? 1 : 0); | |||||
} | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,500 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
A simple class to generate hash functions for some primitive types, intended for | |||||
use with the HashMap class. | |||||
@see HashMap | |||||
*/ | |||||
struct DefaultHashFunctions | |||||
{ | |||||
/** Generates a simple hash from an unsigned int. */ | |||||
static int generateHash (uint32 key, int upperLimit) noexcept { return (int) (key % (uint32) upperLimit); } | |||||
/** Generates a simple hash from an integer. */ | |||||
static int generateHash (int32 key, int upperLimit) noexcept { return generateHash ((uint32) key, upperLimit); } | |||||
/** Generates a simple hash from a uint64. */ | |||||
static int generateHash (uint64 key, int upperLimit) noexcept { return (int) (key % (uint64) upperLimit); } | |||||
/** Generates a simple hash from an int64. */ | |||||
static int generateHash (int64 key, int upperLimit) noexcept { return generateHash ((uint64) key, upperLimit); } | |||||
/** Generates a simple hash from a string. */ | |||||
static int generateHash (const String& key, int upperLimit) noexcept { return generateHash ((uint32) key.hashCode(), upperLimit); } | |||||
/** Generates a simple hash from a variant. */ | |||||
static int generateHash (const var& key, int upperLimit) noexcept { return generateHash (key.toString(), upperLimit); } | |||||
/** Generates a simple hash from a void ptr. */ | |||||
static int generateHash (const void* key, int upperLimit) noexcept { return generateHash ((pointer_sized_uint) key, upperLimit); } | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
Holds a set of mappings between some key/value pairs. | |||||
The types of the key and value objects are set as template parameters. | |||||
You can also specify a class to supply a hash function that converts a key value | |||||
into an hashed integer. This class must have the form: | |||||
@code | |||||
struct MyHashGenerator | |||||
{ | |||||
int generateHash (MyKeyType key, int upperLimit) const | |||||
{ | |||||
// The function must return a value 0 <= x < upperLimit | |||||
return someFunctionOfMyKeyType (key) % upperLimit; | |||||
} | |||||
}; | |||||
@endcode | |||||
Like the Array class, the key and value types are expected to be copy-by-value | |||||
types, so if you define them to be pointer types, this class won't delete the | |||||
objects that they point to. | |||||
If you don't supply a class for the HashFunctionType template parameter, the | |||||
default one provides some simple mappings for strings and ints. | |||||
@code | |||||
HashMap<int, String> hash; | |||||
hash.set (1, "item1"); | |||||
hash.set (2, "item2"); | |||||
DBG (hash [1]); // prints "item1" | |||||
DBG (hash [2]); // prints "item2" | |||||
// This iterates the map, printing all of its key -> value pairs.. | |||||
for (HashMap<int, String>::Iterator i (hash); i.next();) | |||||
DBG (i.getKey() << " -> " << i.getValue()); | |||||
@endcode | |||||
@tparam HashFunctionType The class of hash function, which must be copy-constructible. | |||||
@see CriticalSection, DefaultHashFunctions, NamedValueSet, SortedSet | |||||
*/ | |||||
template <typename KeyType, | |||||
typename ValueType, | |||||
class HashFunctionType = DefaultHashFunctions, | |||||
class TypeOfCriticalSectionToUse = DummyCriticalSection> | |||||
class HashMap | |||||
{ | |||||
private: | |||||
typedef typename TypeHelpers::ParameterType<KeyType>::type KeyTypeParameter; | |||||
typedef typename TypeHelpers::ParameterType<ValueType>::type ValueTypeParameter; | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an empty hash-map. | |||||
@param numberOfSlots Specifies the number of hash entries the map will use. This will be | |||||
the "upperLimit" parameter that is passed to your generateHash() | |||||
function. The number of hash slots will grow automatically if necessary, | |||||
or it can be remapped manually using remapTable(). | |||||
@param hashFunction An instance of HashFunctionType, which will be copied and | |||||
stored to use with the HashMap. This parameter can be omitted | |||||
if HashFunctionType has a default constructor. | |||||
*/ | |||||
explicit HashMap (int numberOfSlots = defaultHashTableSize, | |||||
HashFunctionType hashFunction = HashFunctionType()) | |||||
: hashFunctionToUse (hashFunction), totalNumItems (0) | |||||
{ | |||||
hashSlots.insertMultiple (0, nullptr, numberOfSlots); | |||||
} | |||||
/** Destructor. */ | |||||
~HashMap() | |||||
{ | |||||
clear(); | |||||
} | |||||
//============================================================================== | |||||
/** Removes all values from the map. | |||||
Note that this will clear the content, but won't affect the number of slots (see | |||||
remapTable and getNumSlots). | |||||
*/ | |||||
void clear() | |||||
{ | |||||
const ScopedLockType sl (getLock()); | |||||
for (auto i = hashSlots.size(); --i >= 0;) | |||||
{ | |||||
auto* h = hashSlots.getUnchecked(i); | |||||
while (h != nullptr) | |||||
{ | |||||
const ScopedPointer<HashEntry> deleter (h); | |||||
h = h->nextEntry; | |||||
} | |||||
hashSlots.set (i, nullptr); | |||||
} | |||||
totalNumItems = 0; | |||||
} | |||||
//============================================================================== | |||||
/** Returns the current number of items in the map. */ | |||||
inline int size() const noexcept | |||||
{ | |||||
return totalNumItems; | |||||
} | |||||
/** Returns the value corresponding to a given key. | |||||
If the map doesn't contain the key, a default instance of the value type is returned. | |||||
@param keyToLookFor the key of the item being requested | |||||
*/ | |||||
inline ValueType operator[] (KeyTypeParameter keyToLookFor) const | |||||
{ | |||||
const ScopedLockType sl (getLock()); | |||||
if (auto* entry = getEntry (getSlot (keyToLookFor), keyToLookFor)) | |||||
return entry->value; | |||||
return ValueType(); | |||||
} | |||||
/** Returns a reference to the value corresponding to a given key. | |||||
If the map doesn't contain the key, a default instance of the value type is | |||||
added to the map and a reference to this is returned. | |||||
@param keyToLookFor the key of the item being requested | |||||
*/ | |||||
inline ValueType& getReference (KeyTypeParameter keyToLookFor) | |||||
{ | |||||
const ScopedLockType sl (getLock()); | |||||
auto hashIndex = generateHashFor (keyToLookFor, getNumSlots()); | |||||
auto* firstEntry = hashSlots.getUnchecked (hashIndex); | |||||
if (auto* entry = getEntry (firstEntry, keyToLookFor)) | |||||
return entry->value; | |||||
auto* entry = new HashEntry (keyToLookFor, ValueType(), firstEntry); | |||||
hashSlots.set (hashIndex, entry); | |||||
++totalNumItems; | |||||
if (totalNumItems > (getNumSlots() * 3) / 2) | |||||
remapTable (getNumSlots() * 2); | |||||
return entry->value; | |||||
} | |||||
//============================================================================== | |||||
/** Returns true if the map contains an item with the specied key. */ | |||||
bool contains (KeyTypeParameter keyToLookFor) const | |||||
{ | |||||
const ScopedLockType sl (getLock()); | |||||
return (getEntry (getSlot (keyToLookFor), keyToLookFor) != nullptr); | |||||
} | |||||
/** Returns true if the hash contains at least one occurrence of a given value. */ | |||||
bool containsValue (ValueTypeParameter valueToLookFor) const | |||||
{ | |||||
const ScopedLockType sl (getLock()); | |||||
for (auto i = getNumSlots(); --i >= 0;) | |||||
for (auto* entry = hashSlots.getUnchecked(i); entry != nullptr; entry = entry->nextEntry) | |||||
if (entry->value == valueToLookFor) | |||||
return true; | |||||
return false; | |||||
} | |||||
//============================================================================== | |||||
/** Adds or replaces an element in the hash-map. | |||||
If there's already an item with the given key, this will replace its value. Otherwise, a new item | |||||
will be added to the map. | |||||
*/ | |||||
void set (KeyTypeParameter newKey, ValueTypeParameter newValue) { getReference (newKey) = newValue; } | |||||
/** Removes an item with the given key. */ | |||||
void remove (KeyTypeParameter keyToRemove) | |||||
{ | |||||
const ScopedLockType sl (getLock()); | |||||
auto hashIndex = generateHashFor (keyToRemove, getNumSlots()); | |||||
auto* entry = hashSlots.getUnchecked (hashIndex); | |||||
HashEntry* previous = nullptr; | |||||
while (entry != nullptr) | |||||
{ | |||||
if (entry->key == keyToRemove) | |||||
{ | |||||
const ScopedPointer<HashEntry> deleter (entry); | |||||
entry = entry->nextEntry; | |||||
if (previous != nullptr) | |||||
previous->nextEntry = entry; | |||||
else | |||||
hashSlots.set (hashIndex, entry); | |||||
--totalNumItems; | |||||
} | |||||
else | |||||
{ | |||||
previous = entry; | |||||
entry = entry->nextEntry; | |||||
} | |||||
} | |||||
} | |||||
/** Removes all items with the given value. */ | |||||
void removeValue (ValueTypeParameter valueToRemove) | |||||
{ | |||||
const ScopedLockType sl (getLock()); | |||||
for (auto i = getNumSlots(); --i >= 0;) | |||||
{ | |||||
auto* entry = hashSlots.getUnchecked(i); | |||||
HashEntry* previous = nullptr; | |||||
while (entry != nullptr) | |||||
{ | |||||
if (entry->value == valueToRemove) | |||||
{ | |||||
const ScopedPointer<HashEntry> deleter (entry); | |||||
entry = entry->nextEntry; | |||||
if (previous != nullptr) | |||||
previous->nextEntry = entry; | |||||
else | |||||
hashSlots.set (i, entry); | |||||
--totalNumItems; | |||||
} | |||||
else | |||||
{ | |||||
previous = entry; | |||||
entry = entry->nextEntry; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
/** Remaps the hash-map to use a different number of slots for its hash function. | |||||
Each slot corresponds to a single hash-code, and each one can contain multiple items. | |||||
@see getNumSlots() | |||||
*/ | |||||
void remapTable (int newNumberOfSlots) | |||||
{ | |||||
const ScopedLockType sl (getLock()); | |||||
Array<HashEntry*> newSlots; | |||||
newSlots.insertMultiple (0, nullptr, newNumberOfSlots); | |||||
for (auto i = getNumSlots(); --i >= 0;) | |||||
{ | |||||
HashEntry* nextEntry = nullptr; | |||||
for (auto* entry = hashSlots.getUnchecked(i); entry != nullptr; entry = nextEntry) | |||||
{ | |||||
auto hashIndex = generateHashFor (entry->key, newNumberOfSlots); | |||||
nextEntry = entry->nextEntry; | |||||
entry->nextEntry = newSlots.getUnchecked (hashIndex); | |||||
newSlots.set (hashIndex, entry); | |||||
} | |||||
} | |||||
hashSlots.swapWith (newSlots); | |||||
} | |||||
/** Returns the number of slots which are available for hashing. | |||||
Each slot corresponds to a single hash-code, and each one can contain multiple items. | |||||
@see getNumSlots() | |||||
*/ | |||||
inline int getNumSlots() const noexcept | |||||
{ | |||||
return hashSlots.size(); | |||||
} | |||||
//============================================================================== | |||||
/** Efficiently swaps the contents of two hash-maps. */ | |||||
template <class OtherHashMapType> | |||||
void swapWith (OtherHashMapType& otherHashMap) noexcept | |||||
{ | |||||
const ScopedLockType lock1 (getLock()); | |||||
const typename OtherHashMapType::ScopedLockType lock2 (otherHashMap.getLock()); | |||||
hashSlots.swapWith (otherHashMap.hashSlots); | |||||
std::swap (totalNumItems, otherHashMap.totalNumItems); | |||||
} | |||||
//============================================================================== | |||||
/** Returns the CriticalSection that locks this structure. | |||||
To lock, you can call getLock().enter() and getLock().exit(), or preferably use | |||||
an object of ScopedLockType as an RAII lock for it. | |||||
*/ | |||||
inline const TypeOfCriticalSectionToUse& getLock() const noexcept { return lock; } | |||||
/** Returns the type of scoped lock to use for locking this array */ | |||||
typedef typename TypeOfCriticalSectionToUse::ScopedLockType ScopedLockType; | |||||
private: | |||||
//============================================================================== | |||||
class HashEntry | |||||
{ | |||||
public: | |||||
HashEntry (KeyTypeParameter k, ValueTypeParameter val, HashEntry* const next) | |||||
: key (k), value (val), nextEntry (next) | |||||
{} | |||||
const KeyType key; | |||||
ValueType value; | |||||
HashEntry* nextEntry; | |||||
JUCE_DECLARE_NON_COPYABLE (HashEntry) | |||||
}; | |||||
public: | |||||
//============================================================================== | |||||
/** Iterates over the items in a HashMap. | |||||
To use it, repeatedly call next() until it returns false, e.g. | |||||
@code | |||||
HashMap <String, String> myMap; | |||||
HashMap<String, String>::Iterator i (myMap); | |||||
while (i.next()) | |||||
{ | |||||
DBG (i.getKey() << " -> " << i.getValue()); | |||||
} | |||||
@endcode | |||||
The order in which items are iterated bears no resemblence to the order in which | |||||
they were originally added! | |||||
Obviously as soon as you call any non-const methods on the original hash-map, any | |||||
iterators that were created beforehand will cease to be valid, and should not be used. | |||||
@see HashMap | |||||
*/ | |||||
struct Iterator | |||||
{ | |||||
Iterator (const HashMap& hashMapToIterate) noexcept | |||||
: hashMap (hashMapToIterate), entry (nullptr), index (0) | |||||
{} | |||||
Iterator (const Iterator& other) noexcept | |||||
: hashMap (other.hashMap), entry (other.entry), index (other.index) | |||||
{} | |||||
/** Moves to the next item, if one is available. | |||||
When this returns true, you can get the item's key and value using getKey() and | |||||
getValue(). If it returns false, the iteration has finished and you should stop. | |||||
*/ | |||||
bool next() noexcept | |||||
{ | |||||
if (entry != nullptr) | |||||
entry = entry->nextEntry; | |||||
while (entry == nullptr) | |||||
{ | |||||
if (index >= hashMap.getNumSlots()) | |||||
return false; | |||||
entry = hashMap.hashSlots.getUnchecked (index++); | |||||
} | |||||
return true; | |||||
} | |||||
/** Returns the current item's key. | |||||
This should only be called when a call to next() has just returned true. | |||||
*/ | |||||
KeyType getKey() const | |||||
{ | |||||
return entry != nullptr ? entry->key : KeyType(); | |||||
} | |||||
/** Returns the current item's value. | |||||
This should only be called when a call to next() has just returned true. | |||||
*/ | |||||
ValueType getValue() const | |||||
{ | |||||
return entry != nullptr ? entry->value : ValueType(); | |||||
} | |||||
/** Resets the iterator to its starting position. */ | |||||
void reset() noexcept | |||||
{ | |||||
entry = nullptr; | |||||
index = 0; | |||||
} | |||||
Iterator& operator++() noexcept { next(); return *this; } | |||||
ValueType operator*() const { return getValue(); } | |||||
bool operator!= (const Iterator& other) const noexcept { return entry != other.entry || index != other.index; } | |||||
void resetToEnd() noexcept { index = hashMap.getNumSlots(); } | |||||
private: | |||||
//============================================================================== | |||||
const HashMap& hashMap; | |||||
HashEntry* entry; | |||||
int index; | |||||
// using the copy constructor is ok, but you cannot assign iterators | |||||
Iterator& operator= (const Iterator&) JUCE_DELETED_FUNCTION; | |||||
JUCE_LEAK_DETECTOR (Iterator) | |||||
}; | |||||
/** Returns a start iterator for the values in this tree. */ | |||||
Iterator begin() const noexcept { Iterator i (*this); i.next(); return i; } | |||||
/** Returns an end iterator for the values in this tree. */ | |||||
Iterator end() const noexcept { Iterator i (*this); i.resetToEnd(); return i; } | |||||
private: | |||||
//============================================================================== | |||||
enum { defaultHashTableSize = 101 }; | |||||
friend struct Iterator; | |||||
HashFunctionType hashFunctionToUse; | |||||
Array<HashEntry*> hashSlots; | |||||
int totalNumItems; | |||||
TypeOfCriticalSectionToUse lock; | |||||
int generateHashFor (KeyTypeParameter key, int numSlots) const | |||||
{ | |||||
const int hash = hashFunctionToUse.generateHash (key, numSlots); | |||||
jassert (isPositiveAndBelow (hash, numSlots)); // your hash function is generating out-of-range numbers! | |||||
return hash; | |||||
} | |||||
static inline HashEntry* getEntry (HashEntry* firstEntry, KeyType keyToLookFor) noexcept | |||||
{ | |||||
for (auto* entry = firstEntry; entry != nullptr; entry = entry->nextEntry) | |||||
if (entry->key == keyToLookFor) | |||||
return entry; | |||||
return nullptr; | |||||
} | |||||
inline HashEntry* getSlot (KeyType key) const noexcept { return hashSlots.getUnchecked (generateHashFor (key, getNumSlots())); } | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HashMap) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,276 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
struct HashMapTest : public UnitTest | |||||
{ | |||||
HashMapTest() : UnitTest ("HashMap", "Containers") {} | |||||
void runTest() override | |||||
{ | |||||
doTest<AddElementsTest> ("AddElementsTest"); | |||||
doTest<AccessTest> ("AccessTest"); | |||||
doTest<RemoveTest> ("RemoveTest"); | |||||
doTest<PersistantMemoryLocationOfValues> ("PersistantMemoryLocationOfValues"); | |||||
} | |||||
//============================================================================== | |||||
struct AddElementsTest | |||||
{ | |||||
template <typename KeyType> | |||||
static void run (UnitTest& u) | |||||
{ | |||||
AssociativeMap<KeyType, int> groundTruth; | |||||
HashMap<KeyType, int> hashMap; | |||||
RandomKeys<KeyType> keyOracle (300, 3827829); | |||||
Random valueOracle (48735); | |||||
int totalValues = 0; | |||||
for (int i = 0; i < 10000; ++i) | |||||
{ | |||||
auto key = keyOracle.next(); | |||||
auto value = valueOracle.nextInt(); | |||||
bool contains = (groundTruth.find (key) != nullptr); | |||||
u.expectEquals ((int) contains, (int) hashMap.contains (key)); | |||||
groundTruth.add (key, value); | |||||
hashMap.set (key, value); | |||||
if (! contains) totalValues++; | |||||
u.expectEquals (hashMap.size(), totalValues); | |||||
} | |||||
} | |||||
}; | |||||
struct AccessTest | |||||
{ | |||||
template <typename KeyType> | |||||
static void run (UnitTest& u) | |||||
{ | |||||
AssociativeMap<KeyType, int> groundTruth; | |||||
HashMap<KeyType, int> hashMap; | |||||
fillWithRandomValues (hashMap, groundTruth); | |||||
for (auto pair : groundTruth.pairs) | |||||
u.expectEquals (hashMap[pair.key], pair.value); | |||||
} | |||||
}; | |||||
struct RemoveTest | |||||
{ | |||||
template <typename KeyType> | |||||
static void run (UnitTest& u) | |||||
{ | |||||
AssociativeMap<KeyType, int> groundTruth; | |||||
HashMap<KeyType, int> hashMap; | |||||
fillWithRandomValues (hashMap, groundTruth); | |||||
auto n = groundTruth.size(); | |||||
Random r (3827387); | |||||
for (int i = 0; i < 100; ++i) | |||||
{ | |||||
auto idx = r.nextInt (n-- - 1); | |||||
auto key = groundTruth.pairs.getReference (idx).key; | |||||
groundTruth.pairs.remove (idx); | |||||
hashMap.remove (key); | |||||
u.expect (! hashMap.contains (key)); | |||||
for (auto pair : groundTruth.pairs) | |||||
u.expectEquals (hashMap[pair.key], pair.value); | |||||
} | |||||
} | |||||
}; | |||||
// ensure that the addresses of object references don't change | |||||
struct PersistantMemoryLocationOfValues | |||||
{ | |||||
struct AddressAndValue { int value; const int* valueAddress; }; | |||||
template <typename KeyType> | |||||
static void run (UnitTest& u) | |||||
{ | |||||
AssociativeMap<KeyType, AddressAndValue> groundTruth; | |||||
HashMap<KeyType, int> hashMap; | |||||
RandomKeys<KeyType> keyOracle (300, 3827829); | |||||
Random valueOracle (48735); | |||||
for (int i = 0; i < 1000; ++i) | |||||
{ | |||||
auto key = keyOracle.next(); | |||||
auto value = valueOracle.nextInt(); | |||||
hashMap.set (key, value); | |||||
if (auto* existing = groundTruth.find (key)) | |||||
{ | |||||
// don't change the address: only the value | |||||
existing->value = value; | |||||
} | |||||
else | |||||
{ | |||||
groundTruth.add (key, { value, &hashMap.getReference (key) }); | |||||
} | |||||
for (auto pair : groundTruth.pairs) | |||||
{ | |||||
const auto& hashMapValue = hashMap.getReference (pair.key); | |||||
u.expectEquals (hashMapValue, pair.value.value); | |||||
u.expect (&hashMapValue == pair.value.valueAddress); | |||||
} | |||||
} | |||||
auto n = groundTruth.size(); | |||||
Random r (3827387); | |||||
for (int i = 0; i < 100; ++i) | |||||
{ | |||||
auto idx = r.nextInt (n-- - 1); | |||||
auto key = groundTruth.pairs.getReference (idx).key; | |||||
groundTruth.pairs.remove (idx); | |||||
hashMap.remove (key); | |||||
for (auto pair : groundTruth.pairs) | |||||
{ | |||||
const auto& hashMapValue = hashMap.getReference (pair.key); | |||||
u.expectEquals (hashMapValue, pair.value.value); | |||||
u.expect (&hashMapValue == pair.value.valueAddress); | |||||
} | |||||
} | |||||
} | |||||
}; | |||||
//============================================================================== | |||||
template <class Test> | |||||
void doTest (const String& testName) | |||||
{ | |||||
beginTest (testName); | |||||
Test::template run<int> (*this); | |||||
Test::template run<void*> (*this); | |||||
Test::template run<String> (*this); | |||||
} | |||||
//============================================================================== | |||||
template <typename KeyType, typename ValueType> | |||||
struct AssociativeMap | |||||
{ | |||||
struct KeyValuePair { KeyType key; ValueType value; }; | |||||
ValueType* find (KeyType key) | |||||
{ | |||||
auto n = pairs.size(); | |||||
for (int i = 0; i < n; ++i) | |||||
{ | |||||
auto& pair = pairs.getReference (i); | |||||
if (pair.key == key) | |||||
return &pair.value; | |||||
} | |||||
return nullptr; | |||||
} | |||||
void add (KeyType key, ValueType value) | |||||
{ | |||||
if (ValueType* v = find (key)) | |||||
*v = value; | |||||
else | |||||
pairs.add ({key, value}); | |||||
} | |||||
int size() const { return pairs.size(); } | |||||
Array<KeyValuePair> pairs; | |||||
}; | |||||
template <typename KeyType, typename ValueType> | |||||
static void fillWithRandomValues (HashMap<KeyType, int>& hashMap, AssociativeMap<KeyType, ValueType>& groundTruth) | |||||
{ | |||||
RandomKeys<KeyType> keyOracle (300, 3827829); | |||||
Random valueOracle (48735); | |||||
for (int i = 0; i < 10000; ++i) | |||||
{ | |||||
auto key = keyOracle.next(); | |||||
auto value = valueOracle.nextInt(); | |||||
groundTruth.add (key, value); | |||||
hashMap.set (key, value); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
template <typename KeyType> | |||||
class RandomKeys | |||||
{ | |||||
public: | |||||
RandomKeys (int maxUniqueKeys, int seed) : r (seed) | |||||
{ | |||||
for (int i = 0; i < maxUniqueKeys; ++i) | |||||
keys.add (generateRandomKey (r)); | |||||
} | |||||
const KeyType& next() | |||||
{ | |||||
int i = r.nextInt (keys.size() - 1); | |||||
return keys.getReference (i); | |||||
} | |||||
private: | |||||
static KeyType generateRandomKey (Random&); | |||||
Random r; | |||||
Array<KeyType> keys; | |||||
}; | |||||
}; | |||||
template <> int HashMapTest::RandomKeys<int> ::generateRandomKey (Random& rnd) { return rnd.nextInt(); } | |||||
template <> void* HashMapTest::RandomKeys<void*>::generateRandomKey (Random& rnd) { return reinterpret_cast<void*> (rnd.nextInt64()); } | |||||
template <> String HashMapTest::RandomKeys<String>::generateRandomKey (Random& rnd) | |||||
{ | |||||
String str; | |||||
int len = rnd.nextInt (8)+1; | |||||
for (int i = 0; i < len; ++i) | |||||
str += static_cast<char> (rnd.nextInt (95) + 32); | |||||
return str; | |||||
} | |||||
static HashMapTest hashMapTest; | |||||
} // namespace juce |
@@ -0,0 +1,361 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Helps to manipulate singly-linked lists of objects. | |||||
For objects that are designed to contain a pointer to the subsequent item in the | |||||
list, this class contains methods to deal with the list. To use it, the ObjectType | |||||
class that it points to must contain a LinkedListPointer called nextListItem, e.g. | |||||
@code | |||||
struct MyObject | |||||
{ | |||||
int x, y, z; | |||||
// A linkable object must contain a member with this name and type, which must be | |||||
// accessible by the LinkedListPointer class. (This doesn't mean it has to be public - | |||||
// you could make your class a friend of a LinkedListPointer<MyObject> instead). | |||||
LinkedListPointer<MyObject> nextListItem; | |||||
}; | |||||
LinkedListPointer<MyObject> myList; | |||||
myList.append (new MyObject()); | |||||
myList.append (new MyObject()); | |||||
int numItems = myList.size(); // returns 2 | |||||
MyObject* lastInList = myList.getLast(); | |||||
@endcode | |||||
*/ | |||||
template <class ObjectType> | |||||
class LinkedListPointer | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a null pointer to an empty list. */ | |||||
LinkedListPointer() noexcept | |||||
: item (nullptr) | |||||
{ | |||||
} | |||||
/** Creates a pointer to a list whose head is the item provided. */ | |||||
explicit LinkedListPointer (ObjectType* const headItem) noexcept | |||||
: item (headItem) | |||||
{ | |||||
} | |||||
/** Sets this pointer to point to a new list. */ | |||||
LinkedListPointer& operator= (ObjectType* const newItem) noexcept | |||||
{ | |||||
item = newItem; | |||||
return *this; | |||||
} | |||||
LinkedListPointer (LinkedListPointer&& other) noexcept | |||||
: item (other.item) | |||||
{ | |||||
other.item = nullptr; | |||||
} | |||||
LinkedListPointer& operator= (LinkedListPointer&& other) noexcept | |||||
{ | |||||
jassert (this != &other); // hopefully the compiler should make this situation impossible! | |||||
item = other.item; | |||||
other.item = nullptr; | |||||
return *this; | |||||
} | |||||
//============================================================================== | |||||
/** Returns the item which this pointer points to. */ | |||||
inline operator ObjectType*() const noexcept | |||||
{ | |||||
return item; | |||||
} | |||||
/** Returns the item which this pointer points to. */ | |||||
inline ObjectType* get() const noexcept | |||||
{ | |||||
return item; | |||||
} | |||||
/** Returns the last item in the list which this pointer points to. | |||||
This will iterate the list and return the last item found. Obviously the speed | |||||
of this operation will be proportional to the size of the list. If the list is | |||||
empty the return value will be this object. | |||||
If you're planning on appending a number of items to your list, it's much more | |||||
efficient to use the Appender class than to repeatedly call getLast() to find the end. | |||||
*/ | |||||
LinkedListPointer& getLast() noexcept | |||||
{ | |||||
LinkedListPointer* l = this; | |||||
while (l->item != nullptr) | |||||
l = &(l->item->nextListItem); | |||||
return *l; | |||||
} | |||||
/** Returns the number of items in the list. | |||||
Obviously with a simple linked list, getting the size involves iterating the list, so | |||||
this can be a lengthy operation - be careful when using this method in your code. | |||||
*/ | |||||
int size() const noexcept | |||||
{ | |||||
int total = 0; | |||||
for (ObjectType* i = item; i != nullptr; i = i->nextListItem) | |||||
++total; | |||||
return total; | |||||
} | |||||
/** Returns the item at a given index in the list. | |||||
Since the only way to find an item is to iterate the list, this operation can obviously | |||||
be slow, depending on its size, so you should be careful when using this in algorithms. | |||||
*/ | |||||
LinkedListPointer& operator[] (int index) noexcept | |||||
{ | |||||
LinkedListPointer* l = this; | |||||
while (--index >= 0 && l->item != nullptr) | |||||
l = &(l->item->nextListItem); | |||||
return *l; | |||||
} | |||||
/** Returns the item at a given index in the list. | |||||
Since the only way to find an item is to iterate the list, this operation can obviously | |||||
be slow, depending on its size, so you should be careful when using this in algorithms. | |||||
*/ | |||||
const LinkedListPointer& operator[] (int index) const noexcept | |||||
{ | |||||
const LinkedListPointer* l = this; | |||||
while (--index >= 0 && l->item != nullptr) | |||||
l = &(l->item->nextListItem); | |||||
return *l; | |||||
} | |||||
/** Returns true if the list contains the given item. */ | |||||
bool contains (const ObjectType* const itemToLookFor) const noexcept | |||||
{ | |||||
for (ObjectType* i = item; i != nullptr; i = i->nextListItem) | |||||
if (itemToLookFor == i) | |||||
return true; | |||||
return false; | |||||
} | |||||
//============================================================================== | |||||
/** Inserts an item into the list, placing it before the item that this pointer | |||||
currently points to. | |||||
*/ | |||||
void insertNext (ObjectType* const newItem) | |||||
{ | |||||
jassert (newItem != nullptr); | |||||
jassert (newItem->nextListItem == nullptr); | |||||
newItem->nextListItem = item; | |||||
item = newItem; | |||||
} | |||||
/** Inserts an item at a numeric index in the list. | |||||
Obviously this will involve iterating the list to find the item at the given index, | |||||
so be careful about the impact this may have on execution time. | |||||
*/ | |||||
void insertAtIndex (int index, ObjectType* newItem) | |||||
{ | |||||
jassert (newItem != nullptr); | |||||
LinkedListPointer* l = this; | |||||
while (index != 0 && l->item != nullptr) | |||||
{ | |||||
l = &(l->item->nextListItem); | |||||
--index; | |||||
} | |||||
l->insertNext (newItem); | |||||
} | |||||
/** Replaces the object that this pointer points to, appending the rest of the list to | |||||
the new object, and returning the old one. | |||||
*/ | |||||
ObjectType* replaceNext (ObjectType* const newItem) noexcept | |||||
{ | |||||
jassert (newItem != nullptr); | |||||
jassert (newItem->nextListItem == nullptr); | |||||
ObjectType* const oldItem = item; | |||||
item = newItem; | |||||
item->nextListItem = oldItem->nextListItem.item; | |||||
oldItem->nextListItem.item = nullptr; | |||||
return oldItem; | |||||
} | |||||
/** Adds an item to the end of the list. | |||||
This operation involves iterating the whole list, so can be slow - if you need to | |||||
append a number of items to your list, it's much more efficient to use the Appender | |||||
class than to repeatedly call append(). | |||||
*/ | |||||
void append (ObjectType* const newItem) | |||||
{ | |||||
getLast().item = newItem; | |||||
} | |||||
/** Creates copies of all the items in another list and adds them to this one. | |||||
This will use the ObjectType's copy constructor to try to create copies of each | |||||
item in the other list, and appends them to this list. | |||||
*/ | |||||
void addCopyOfList (const LinkedListPointer& other) | |||||
{ | |||||
LinkedListPointer* insertPoint = this; | |||||
for (ObjectType* i = other.item; i != nullptr; i = i->nextListItem) | |||||
{ | |||||
insertPoint->insertNext (new ObjectType (*i)); | |||||
insertPoint = &(insertPoint->item->nextListItem); | |||||
} | |||||
} | |||||
/** Removes the head item from the list. | |||||
This won't delete the object that is removed, but returns it, so the caller can | |||||
delete it if necessary. | |||||
*/ | |||||
ObjectType* removeNext() noexcept | |||||
{ | |||||
ObjectType* const oldItem = item; | |||||
if (oldItem != nullptr) | |||||
{ | |||||
item = oldItem->nextListItem; | |||||
oldItem->nextListItem.item = nullptr; | |||||
} | |||||
return oldItem; | |||||
} | |||||
/** Removes a specific item from the list. | |||||
Note that this will not delete the item, it simply unlinks it from the list. | |||||
*/ | |||||
void remove (ObjectType* const itemToRemove) | |||||
{ | |||||
if (LinkedListPointer* const l = findPointerTo (itemToRemove)) | |||||
l->removeNext(); | |||||
} | |||||
/** Iterates the list, calling the delete operator on all of its elements and | |||||
leaving this pointer empty. | |||||
*/ | |||||
void deleteAll() | |||||
{ | |||||
while (item != nullptr) | |||||
{ | |||||
ObjectType* const oldItem = item; | |||||
item = oldItem->nextListItem; | |||||
delete oldItem; | |||||
} | |||||
} | |||||
/** Finds a pointer to a given item. | |||||
If the item is found in the list, this returns the pointer that points to it. If | |||||
the item isn't found, this returns null. | |||||
*/ | |||||
LinkedListPointer* findPointerTo (ObjectType* const itemToLookFor) noexcept | |||||
{ | |||||
LinkedListPointer* l = this; | |||||
while (l->item != nullptr) | |||||
{ | |||||
if (l->item == itemToLookFor) | |||||
return l; | |||||
l = &(l->item->nextListItem); | |||||
} | |||||
return nullptr; | |||||
} | |||||
/** Copies the items in the list to an array. | |||||
The destArray must contain enough elements to hold the entire list - no checks are | |||||
made for this! | |||||
*/ | |||||
void copyToArray (ObjectType** destArray) const noexcept | |||||
{ | |||||
jassert (destArray != nullptr); | |||||
for (ObjectType* i = item; i != nullptr; i = i->nextListItem) | |||||
*destArray++ = i; | |||||
} | |||||
/** Swaps this pointer with another one */ | |||||
void swapWith (LinkedListPointer& other) noexcept | |||||
{ | |||||
std::swap (item, other.item); | |||||
} | |||||
//============================================================================== | |||||
/** | |||||
Allows efficient repeated insertions into a list. | |||||
You can create an Appender object which points to the last element in your | |||||
list, and then repeatedly call Appender::append() to add items to the end | |||||
of the list in O(1) time. | |||||
*/ | |||||
class Appender | |||||
{ | |||||
public: | |||||
/** Creates an appender which will add items to the given list. | |||||
*/ | |||||
Appender (LinkedListPointer& endOfListPointer) noexcept | |||||
: endOfList (&endOfListPointer) | |||||
{ | |||||
// This can only be used to add to the end of a list. | |||||
jassert (endOfListPointer.item == nullptr); | |||||
} | |||||
/** Appends an item to the list. */ | |||||
void append (ObjectType* const newItem) noexcept | |||||
{ | |||||
*endOfList = newItem; | |||||
endOfList = &(newItem->nextListItem); | |||||
} | |||||
private: | |||||
LinkedListPointer* endOfList; | |||||
JUCE_DECLARE_NON_COPYABLE (Appender) | |||||
}; | |||||
private: | |||||
//============================================================================== | |||||
ObjectType* item; | |||||
JUCE_DECLARE_NON_COPYABLE (LinkedListPointer) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,189 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
#if JUCE_UNIT_TESTS | |||||
struct ListenerBase | |||||
{ | |||||
ListenerBase (int& counter) : c (counter) {} | |||||
virtual ~ListenerBase() {} | |||||
// Required to supress VS2013 compiler warnings | |||||
ListenerBase& operator= (const ListenerBase&) = delete; | |||||
virtual void f () = 0; | |||||
virtual void f (void*) = 0; | |||||
virtual void f (void*, void*) = 0; | |||||
virtual void f (void*, void*, void*) = 0; | |||||
virtual void f (void*, void*, void*, void*) = 0; | |||||
virtual void f (void*, void*, void*, void*, void*) = 0; | |||||
virtual void f (void*, void*, void*, void*, void*, void*) = 0; | |||||
int& c; | |||||
}; | |||||
struct Listener1 : public ListenerBase | |||||
{ | |||||
Listener1 (int& counter) : ListenerBase (counter) {} | |||||
// Required to supress VS2013 compiler warnings | |||||
Listener1& operator= (const Listener1&) = delete; | |||||
void f () override { c += 1; } | |||||
void f (void*) override { c += 2; } | |||||
void f (void*, void*) override { c += 3; } | |||||
void f (void*, void*, void*) override { c += 4; } | |||||
void f (void*, void*, void*, void*) override { c += 5; } | |||||
void f (void*, void*, void*, void*, void*) override { c += 6; } | |||||
void f (void*, void*, void*, void*, void*, void*) override { c += 7; } | |||||
}; | |||||
struct Listener2 : public ListenerBase | |||||
{ | |||||
Listener2 (int& counter) : ListenerBase (counter) {} | |||||
// Required to supress VS2013 compiler warnings | |||||
Listener1& operator= (const Listener1&) = delete; | |||||
void f () override { c -= 2; } | |||||
void f (void*) override { c -= 4; } | |||||
void f (void*, void*) override { c -= 6; } | |||||
void f (void*, void*, void*) override { c -= 8; } | |||||
void f (void*, void*, void*, void*) override { c -= 10; } | |||||
void f (void*, void*, void*, void*, void*) override { c -= 12; } | |||||
void f (void*, void*, void*, void*, void*, void*) override { c -= 14; } | |||||
}; | |||||
class ListenerListTests : public UnitTest | |||||
{ | |||||
public: | |||||
ListenerListTests() : UnitTest ("ListenerList", "Containers") {} | |||||
template <typename... Args> | |||||
void callHelper (std::vector<int>& expectedCounterValues) | |||||
{ | |||||
counter = 0; | |||||
listeners.call (&ListenerBase::f); | |||||
expect (counter == expectedCounterValues[0]); | |||||
ListenerList<ListenerBase>::DummyBailOutChecker boc; | |||||
counter = 0; | |||||
listeners.callChecked (boc, &ListenerBase::f); | |||||
expect (counter == expectedCounterValues[0]); | |||||
} | |||||
template<typename T, typename... Args> | |||||
void callHelper (std::vector<int>& expectedCounterValues, T first, Args... args) | |||||
{ | |||||
const int expected = expectedCounterValues[sizeof... (args) + 1]; | |||||
counter = 0; | |||||
listeners.call (&ListenerBase::f, first, args...); | |||||
expect (counter == expected); | |||||
ListenerList<ListenerBase>::DummyBailOutChecker boc; | |||||
counter = 0; | |||||
listeners.callChecked (boc, &ListenerBase::f, first, args...); | |||||
expect (counter == expected); | |||||
callHelper (expectedCounterValues, args...); | |||||
} | |||||
template <typename... Args> | |||||
void callExcludingHelper (ListenerBase* listenerToExclude, | |||||
std::vector<int>& expectedCounterValues) | |||||
{ | |||||
counter = 0; | |||||
listeners.callExcluding (listenerToExclude, &ListenerBase::f); | |||||
expect (counter == expectedCounterValues[0]); | |||||
ListenerList<ListenerBase>::DummyBailOutChecker boc; | |||||
counter = 0; | |||||
listeners.callCheckedExcluding (listenerToExclude, boc, &ListenerBase::f); | |||||
expect (counter == expectedCounterValues[0]); | |||||
} | |||||
template<typename T, typename... Args> | |||||
void callExcludingHelper (ListenerBase* listenerToExclude, | |||||
std::vector<int>& expectedCounterValues, T first, Args... args) | |||||
{ | |||||
const int expected = expectedCounterValues[sizeof... (args) + 1]; | |||||
counter = 0; | |||||
listeners.callExcluding (listenerToExclude, &ListenerBase::f, first, args...); | |||||
expect (counter == expected); | |||||
ListenerList<ListenerBase>::DummyBailOutChecker boc; | |||||
counter = 0; | |||||
listeners.callCheckedExcluding (listenerToExclude, boc, &ListenerBase::f, first, args...); | |||||
expect (counter == expected); | |||||
callExcludingHelper (listenerToExclude, expectedCounterValues, args...); | |||||
} | |||||
void runTest() override | |||||
{ | |||||
counter = 0; | |||||
beginTest ("Call single listener"); | |||||
listeners.add (&listener1); | |||||
std::vector<int> expectedCounterValues; | |||||
for (int i = 1; i < 8; ++i) | |||||
expectedCounterValues.push_back (i); | |||||
callHelper (expectedCounterValues, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); | |||||
beginTest ("Call multiple listeners"); | |||||
listeners.add (&listener2); | |||||
expectedCounterValues.clear(); | |||||
for (int i = 1; i < 8; ++i) | |||||
expectedCounterValues.push_back (-i); | |||||
callHelper (expectedCounterValues, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); | |||||
beginTest ("Call listeners excluding"); | |||||
expectedCounterValues.clear(); | |||||
for (int i = 1; i < 8; ++i) | |||||
expectedCounterValues.push_back (i); | |||||
callExcludingHelper (&listener2, expectedCounterValues, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); | |||||
listeners.remove (&listener1); | |||||
listeners.remove (&listener2); | |||||
} | |||||
int counter = 0; | |||||
ListenerList<ListenerBase> listeners; | |||||
Listener1 listener1 {counter}; | |||||
Listener2 listener2 {counter}; | |||||
}; | |||||
static ListenerListTests listenerListTests; | |||||
#endif | |||||
} // namespace juce |
@@ -0,0 +1,544 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Holds a set of objects and can invoke a member function callback on each object | |||||
in the set with a single call. | |||||
Use a ListenerList to manage a set of objects which need a callback, and you | |||||
can invoke a member function by simply calling call() or callChecked(). | |||||
E.g. | |||||
@code | |||||
class MyListenerType | |||||
{ | |||||
public: | |||||
void myCallbackMethod (int foo, bool bar); | |||||
}; | |||||
ListenerList <MyListenerType> listeners; | |||||
listeners.add (someCallbackObjects...); | |||||
// This will invoke myCallbackMethod (1234, true) on each of the objects | |||||
// in the list... | |||||
listeners.call (&MyListenerType::myCallbackMethod, 1234, true); | |||||
@endcode | |||||
If you add or remove listeners from the list during one of the callbacks - i.e. while | |||||
it's in the middle of iterating the listeners, then it's guaranteed that no listeners | |||||
will be mistakenly called after they've been removed, but it may mean that some of the | |||||
listeners could be called more than once, or not at all, depending on the list's order. | |||||
Sometimes, there's a chance that invoking one of the callbacks might result in the | |||||
list itself being deleted while it's still iterating - to survive this situation, you can | |||||
use callChecked() instead of call(), passing it a local object to act as a "BailOutChecker". | |||||
The BailOutChecker must implement a method of the form "bool shouldBailOut()", and | |||||
the list will check this after each callback to determine whether it should abort the | |||||
operation. For an example of a bail-out checker, see the Component::BailOutChecker class, | |||||
which can be used to check when a Component has been deleted. See also | |||||
ListenerList::DummyBailOutChecker, which is a dummy checker that always returns false. | |||||
*/ | |||||
template <class ListenerClass, | |||||
class ArrayType = Array<ListenerClass*> > | |||||
class ListenerList | |||||
{ | |||||
#ifndef DOXYGEN | |||||
#define LL_TEMPLATE(a) typename P##a | |||||
#define LL_PARAM(a) typename TypeHelpers::ParameterType<P##a>::type param##a | |||||
#endif | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an empty list. */ | |||||
ListenerList() | |||||
{ | |||||
} | |||||
/** Destructor. */ | |||||
~ListenerList() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
/** Adds a listener to the list. | |||||
A listener can only be added once, so if the listener is already in the list, | |||||
this method has no effect. | |||||
@see remove | |||||
*/ | |||||
void add (ListenerClass* const listenerToAdd) | |||||
{ | |||||
// Listeners can't be null pointers! | |||||
jassert (listenerToAdd != nullptr); | |||||
if (listenerToAdd != nullptr) | |||||
listeners.addIfNotAlreadyThere (listenerToAdd); | |||||
} | |||||
/** Removes a listener from the list. | |||||
If the listener wasn't in the list, this has no effect. | |||||
*/ | |||||
void remove (ListenerClass* const listenerToRemove) | |||||
{ | |||||
// Listeners can't be null pointers! | |||||
jassert (listenerToRemove != nullptr); | |||||
listeners.removeFirstMatchingValue (listenerToRemove); | |||||
} | |||||
/** Returns the number of registered listeners. */ | |||||
int size() const noexcept | |||||
{ | |||||
return listeners.size(); | |||||
} | |||||
/** Returns true if any listeners are registered. */ | |||||
bool isEmpty() const noexcept | |||||
{ | |||||
return listeners.size() == 0; | |||||
} | |||||
/** Clears the list. */ | |||||
void clear() | |||||
{ | |||||
listeners.clear(); | |||||
} | |||||
/** Returns true if the specified listener has been added to the list. */ | |||||
bool contains (ListenerClass* const listener) const noexcept | |||||
{ | |||||
return listeners.contains (listener); | |||||
} | |||||
//============================================================================== | |||||
/** Calls a member function on each listener in the list, with no parameters. */ | |||||
void call (void (ListenerClass::*callbackFunction) ()) | |||||
{ | |||||
callChecked (static_cast<const DummyBailOutChecker&> (DummyBailOutChecker()), callbackFunction); | |||||
} | |||||
/** Calls a member function, with no parameters, on all but the specified listener in the list. | |||||
This can be useful if the caller is also a listener and needs to exclude itself. | |||||
*/ | |||||
void callExcluding (ListenerClass* listenerToExclude, void (ListenerClass::*callbackFunction) ()) | |||||
{ | |||||
callCheckedExcluding (listenerToExclude, | |||||
static_cast<const DummyBailOutChecker&> (DummyBailOutChecker()), callbackFunction); | |||||
} | |||||
/** Calls a member function on each listener in the list, with no parameters and a bail-out-checker. | |||||
See the class description for info about writing a bail-out checker. | |||||
*/ | |||||
template <class BailOutCheckerType> | |||||
void callChecked (const BailOutCheckerType& bailOutChecker, | |||||
void (ListenerClass::*callbackFunction) ()) | |||||
{ | |||||
for (Iterator<BailOutCheckerType, ThisType> iter (*this); iter.next (bailOutChecker);) | |||||
(iter.getListener()->*callbackFunction) (); | |||||
} | |||||
/** Calls a member function on all but the specified listener in the list with a bail-out-checker. | |||||
This can be useful if the caller is also a listener and needs to exclude itself. See the class | |||||
description for info about writing a bail-out checker. | |||||
*/ | |||||
template <class BailOutCheckerType> | |||||
void callCheckedExcluding (ListenerClass* listenerToExclude, | |||||
const BailOutCheckerType& bailOutChecker, | |||||
void (ListenerClass::*callbackFunction) ()) | |||||
{ | |||||
for (Iterator<BailOutCheckerType, ThisType> iter (*this); iter.next (bailOutChecker);) | |||||
if (iter.getListener() != listenerToExclude) | |||||
(iter.getListener()->*callbackFunction) (); | |||||
} | |||||
//============================================================================== | |||||
/** Calls a member function on each listener in the list, with 1 parameter. */ | |||||
template <LL_TEMPLATE(1)> | |||||
void call (void (ListenerClass::*callbackFunction) (P1), LL_PARAM(1)) | |||||
{ | |||||
for (Iterator<DummyBailOutChecker, ThisType> iter (*this); iter.next();) | |||||
(iter.getListener()->*callbackFunction) (param1); | |||||
} | |||||
/** Calls a member function, with 1 parameter, on all but the specified listener in the list. | |||||
This can be useful if the caller is also a listener and needs to exclude itself. | |||||
*/ | |||||
template <LL_TEMPLATE(1)> | |||||
void callExcluding (ListenerClass* listenerToExclude, | |||||
void (ListenerClass::*callbackFunction) (P1), LL_PARAM(1)) | |||||
{ | |||||
for (Iterator<DummyBailOutChecker, ThisType> iter (*this); iter.next();) | |||||
if (iter.getListener() != listenerToExclude) | |||||
(iter.getListener()->*callbackFunction) (param1); | |||||
} | |||||
/** Calls a member function on each listener in the list, with 1 parameter and a bail-out-checker. | |||||
See the class description for info about writing a bail-out checker. | |||||
*/ | |||||
template <class BailOutCheckerType, LL_TEMPLATE(1)> | |||||
void callChecked (const BailOutCheckerType& bailOutChecker, | |||||
void (ListenerClass::*callbackFunction) (P1), | |||||
LL_PARAM(1)) | |||||
{ | |||||
for (Iterator<BailOutCheckerType, ThisType> iter (*this); iter.next (bailOutChecker);) | |||||
(iter.getListener()->*callbackFunction) (param1); | |||||
} | |||||
/** Calls a member function, with 1 parameter, on all but the specified listener in the list | |||||
with a bail-out-checker. This can be useful if the caller is also a listener and needs to | |||||
exclude itself. See the class description for info about writing a bail-out checker. | |||||
*/ | |||||
template <class BailOutCheckerType, LL_TEMPLATE(1)> | |||||
void callCheckedExcluding (ListenerClass* listenerToExclude, | |||||
const BailOutCheckerType& bailOutChecker, | |||||
void (ListenerClass::*callbackFunction) (P1), | |||||
LL_PARAM(1)) | |||||
{ | |||||
for (Iterator<BailOutCheckerType, ThisType> iter (*this); iter.next (bailOutChecker);) | |||||
if (iter.getListener() != listenerToExclude) | |||||
(iter.getListener()->*callbackFunction) (param1); | |||||
} | |||||
//============================================================================== | |||||
/** Calls a member function on each listener in the list, with 2 parameters. */ | |||||
template <LL_TEMPLATE(1), LL_TEMPLATE(2)> | |||||
void call (void (ListenerClass::*callbackFunction) (P1, P2), | |||||
LL_PARAM(1), LL_PARAM(2)) | |||||
{ | |||||
for (Iterator<DummyBailOutChecker, ThisType> iter (*this); iter.next();) | |||||
(iter.getListener()->*callbackFunction) (param1, param2); | |||||
} | |||||
/** Calls a member function, with 2 parameters, on all but the specified listener in the list. | |||||
This can be useful if the caller is also a listener and needs to exclude itself. | |||||
*/ | |||||
template <LL_TEMPLATE(1), LL_TEMPLATE(2)> | |||||
void callExcluding (ListenerClass* listenerToExclude, | |||||
void (ListenerClass::*callbackFunction) (P1, P2), | |||||
LL_PARAM(1), LL_PARAM(2)) | |||||
{ | |||||
for (Iterator<DummyBailOutChecker, ThisType> iter (*this); iter.next();) | |||||
if (iter.getListener() != listenerToExclude) | |||||
(iter.getListener()->*callbackFunction) (param1, param2); | |||||
} | |||||
/** Calls a member function on each listener in the list, with 2 parameters and a bail-out-checker. | |||||
See the class description for info about writing a bail-out checker. | |||||
*/ | |||||
template <class BailOutCheckerType, LL_TEMPLATE(1), LL_TEMPLATE(2)> | |||||
void callChecked (const BailOutCheckerType& bailOutChecker, | |||||
void (ListenerClass::*callbackFunction) (P1, P2), | |||||
LL_PARAM(1), LL_PARAM(2)) | |||||
{ | |||||
for (Iterator<BailOutCheckerType, ThisType> iter (*this); iter.next (bailOutChecker);) | |||||
(iter.getListener()->*callbackFunction) (param1, param2); | |||||
} | |||||
/** Calls a member function, with 2 parameters, on all but the specified listener in the list | |||||
with a bail-out-checker. This can be useful if the caller is also a listener and needs to | |||||
exclude itself. See the class description for info about writing a bail-out checker. | |||||
*/ | |||||
template <class BailOutCheckerType, LL_TEMPLATE(1), LL_TEMPLATE(2)> | |||||
void callCheckedExcluding (ListenerClass* listenerToExclude, | |||||
const BailOutCheckerType& bailOutChecker, | |||||
void (ListenerClass::*callbackFunction) (P1, P2), | |||||
LL_PARAM(1), LL_PARAM(2)) | |||||
{ | |||||
for (Iterator<BailOutCheckerType, ThisType> iter (*this); iter.next (bailOutChecker);) | |||||
if (iter.getListener() != listenerToExclude) | |||||
(iter.getListener()->*callbackFunction) (param1, param2); | |||||
} | |||||
//============================================================================== | |||||
/** Calls a member function on each listener in the list, with 3 parameters. */ | |||||
template <LL_TEMPLATE(1), LL_TEMPLATE(2), LL_TEMPLATE(3)> | |||||
void call (void (ListenerClass::*callbackFunction) (P1, P2, P3), | |||||
LL_PARAM(1), LL_PARAM(2), LL_PARAM(3)) | |||||
{ | |||||
for (Iterator<DummyBailOutChecker, ThisType> iter (*this); iter.next();) | |||||
(iter.getListener()->*callbackFunction) (param1, param2, param3); | |||||
} | |||||
/** Calls a member function, with 3 parameters, on all but the specified listener in the list. | |||||
This can be useful if the caller is also a listener and needs to exclude itself. | |||||
*/ | |||||
template <LL_TEMPLATE(1), LL_TEMPLATE(2), LL_TEMPLATE(3)> | |||||
void callExcluding (ListenerClass* listenerToExclude, | |||||
void (ListenerClass::*callbackFunction) (P1, P2, P3), | |||||
LL_PARAM(1), LL_PARAM(2), LL_PARAM(3)) | |||||
{ | |||||
for (Iterator<DummyBailOutChecker, ThisType> iter (*this); iter.next();) | |||||
if (iter.getListener() != listenerToExclude) | |||||
(iter.getListener()->*callbackFunction) (param1, param2, param3); | |||||
} | |||||
/** Calls a member function on each listener in the list, with 3 parameters and a bail-out-checker. | |||||
See the class description for info about writing a bail-out checker. | |||||
*/ | |||||
template <class BailOutCheckerType, LL_TEMPLATE(1), LL_TEMPLATE(2), LL_TEMPLATE(3)> | |||||
void callChecked (const BailOutCheckerType& bailOutChecker, | |||||
void (ListenerClass::*callbackFunction) (P1, P2, P3), | |||||
LL_PARAM(1), LL_PARAM(2), LL_PARAM(3)) | |||||
{ | |||||
for (Iterator<BailOutCheckerType, ThisType> iter (*this); iter.next (bailOutChecker);) | |||||
(iter.getListener()->*callbackFunction) (param1, param2, param3); | |||||
} | |||||
/** Calls a member function, with 3 parameters, on all but the specified listener in the list | |||||
with a bail-out-checker. This can be useful if the caller is also a listener and needs to | |||||
exclude itself. See the class description for info about writing a bail-out checker. | |||||
*/ | |||||
template <class BailOutCheckerType, LL_TEMPLATE(1), LL_TEMPLATE(2), LL_TEMPLATE(3)> | |||||
void callCheckedExcluding (ListenerClass* listenerToExclude, | |||||
const BailOutCheckerType& bailOutChecker, | |||||
void (ListenerClass::*callbackFunction) (P1, P2, P3), | |||||
LL_PARAM(1), LL_PARAM(2), LL_PARAM(3)) | |||||
{ | |||||
for (Iterator<BailOutCheckerType, ThisType> iter (*this); iter.next (bailOutChecker);) | |||||
if (iter.getListener() != listenerToExclude) | |||||
(iter.getListener()->*callbackFunction) (param1, param2, param3); | |||||
} | |||||
//============================================================================== | |||||
/** Calls a member function on each listener in the list, with 4 parameters. */ | |||||
template <LL_TEMPLATE(1), LL_TEMPLATE(2), LL_TEMPLATE(3), LL_TEMPLATE(4)> | |||||
void call (void (ListenerClass::*callbackFunction) (P1, P2, P3, P4), | |||||
LL_PARAM(1), LL_PARAM(2), LL_PARAM(3), LL_PARAM(4)) | |||||
{ | |||||
for (Iterator<DummyBailOutChecker, ThisType> iter (*this); iter.next();) | |||||
(iter.getListener()->*callbackFunction) (param1, param2, param3, param4); | |||||
} | |||||
/** Calls a member function, with 4 parameters, on all but the specified listener in the list. | |||||
This can be useful if the caller is also a listener and needs to exclude itself. | |||||
*/ | |||||
template <LL_TEMPLATE(1), LL_TEMPLATE(2), LL_TEMPLATE(3), LL_TEMPLATE(4)> | |||||
void callExcluding (ListenerClass* listenerToExclude, | |||||
void (ListenerClass::*callbackFunction) (P1, P2, P3, P4), | |||||
LL_PARAM(1), LL_PARAM(2), LL_PARAM(3), LL_PARAM(4)) | |||||
{ | |||||
for (Iterator<DummyBailOutChecker, ThisType> iter (*this); iter.next();) | |||||
if (iter.getListener() != listenerToExclude) | |||||
(iter.getListener()->*callbackFunction) (param1, param2, param3, param4); | |||||
} | |||||
/** Calls a member function on each listener in the list, with 4 parameters and a bail-out-checker. | |||||
See the class description for info about writing a bail-out checker. | |||||
*/ | |||||
template <class BailOutCheckerType, LL_TEMPLATE(1), LL_TEMPLATE(2), LL_TEMPLATE(3), LL_TEMPLATE(4)> | |||||
void callChecked (const BailOutCheckerType& bailOutChecker, | |||||
void (ListenerClass::*callbackFunction) (P1, P2, P3, P4), | |||||
LL_PARAM(1), LL_PARAM(2), LL_PARAM(3), LL_PARAM(4)) | |||||
{ | |||||
for (Iterator<BailOutCheckerType, ThisType> iter (*this); iter.next (bailOutChecker);) | |||||
(iter.getListener()->*callbackFunction) (param1, param2, param3, param4); | |||||
} | |||||
/** Calls a member function, with 4 parameters, on all but the specified listener in the list | |||||
with a bail-out-checker. This can be useful if the caller is also a listener and needs to | |||||
exclude itself. See the class description for info about writing a bail-out checker. | |||||
*/ | |||||
template <class BailOutCheckerType, LL_TEMPLATE(1), LL_TEMPLATE(2), LL_TEMPLATE(3), LL_TEMPLATE(4)> | |||||
void callCheckedExcluding (ListenerClass* listenerToExclude, | |||||
const BailOutCheckerType& bailOutChecker, | |||||
void (ListenerClass::*callbackFunction) (P1, P2, P3, P4), | |||||
LL_PARAM(1), LL_PARAM(2), LL_PARAM(3), LL_PARAM(4)) | |||||
{ | |||||
for (Iterator<BailOutCheckerType, ThisType> iter (*this); iter.next (bailOutChecker);) | |||||
if (iter.getListener() != listenerToExclude) | |||||
(iter.getListener()->*callbackFunction) (param1, param2, param3, param4); | |||||
} | |||||
//============================================================================== | |||||
/** Calls a member function on each listener in the list, with 5 parameters. */ | |||||
template <LL_TEMPLATE(1), LL_TEMPLATE(2), LL_TEMPLATE(3), LL_TEMPLATE(4), LL_TEMPLATE(5)> | |||||
void call (void (ListenerClass::*callbackFunction) (P1, P2, P3, P4, P5), | |||||
LL_PARAM(1), LL_PARAM(2), LL_PARAM(3), LL_PARAM(4), LL_PARAM(5)) | |||||
{ | |||||
for (Iterator<DummyBailOutChecker, ThisType> iter (*this); iter.next();) | |||||
(iter.getListener()->*callbackFunction) (param1, param2, param3, param4, param5); | |||||
} | |||||
/** Calls a member function, with 5 parameters, on all but the specified listener in the list. | |||||
This can be useful if the caller is also a listener and needs to exclude itself. | |||||
*/ | |||||
template <LL_TEMPLATE(1), LL_TEMPLATE(2), LL_TEMPLATE(3), LL_TEMPLATE(4), LL_TEMPLATE(5)> | |||||
void callExcluding (ListenerClass* listenerToExclude, | |||||
void (ListenerClass::*callbackFunction) (P1, P2, P3, P4, P5), | |||||
LL_PARAM(1), LL_PARAM(2), LL_PARAM(3), LL_PARAM(4), LL_PARAM(5)) | |||||
{ | |||||
for (Iterator<DummyBailOutChecker, ThisType> iter (*this); iter.next();) | |||||
if (iter.getListener() != listenerToExclude) | |||||
(iter.getListener()->*callbackFunction) (param1, param2, param3, param4, param5); | |||||
} | |||||
/** Calls a member function on each listener in the list, with 5 parameters and a bail-out-checker. | |||||
See the class description for info about writing a bail-out checker. | |||||
*/ | |||||
template <class BailOutCheckerType, LL_TEMPLATE(1), LL_TEMPLATE(2), LL_TEMPLATE(3), LL_TEMPLATE(4), LL_TEMPLATE(5)> | |||||
void callChecked (const BailOutCheckerType& bailOutChecker, | |||||
void (ListenerClass::*callbackFunction) (P1, P2, P3, P4, P5), | |||||
LL_PARAM(1), LL_PARAM(2), LL_PARAM(3), LL_PARAM(4), LL_PARAM(5)) | |||||
{ | |||||
for (Iterator<BailOutCheckerType, ThisType> iter (*this); iter.next (bailOutChecker);) | |||||
(iter.getListener()->*callbackFunction) (param1, param2, param3, param4, param5); | |||||
} | |||||
/** Calls a member function, with 5 parameters, on all but the specified listener in the list | |||||
with a bail-out-checker. This can be useful if the caller is also a listener and needs to | |||||
exclude itself. See the class description for info about writing a bail-out checker. | |||||
*/ | |||||
template <class BailOutCheckerType, LL_TEMPLATE(1), LL_TEMPLATE(2), LL_TEMPLATE(3), LL_TEMPLATE(4), LL_TEMPLATE(5)> | |||||
void callCheckedExcluding (ListenerClass* listenerToExclude, | |||||
const BailOutCheckerType& bailOutChecker, | |||||
void (ListenerClass::*callbackFunction) (P1, P2, P3, P4, P5), | |||||
LL_PARAM(1), LL_PARAM(2), LL_PARAM(3), LL_PARAM(4), LL_PARAM(5)) | |||||
{ | |||||
for (Iterator<BailOutCheckerType, ThisType> iter (*this); iter.next (bailOutChecker);) | |||||
if (iter.getListener() != listenerToExclude) | |||||
(iter.getListener()->*callbackFunction) (param1, param2, param3, param4, param5); | |||||
} | |||||
//============================================================================== | |||||
/** Calls a member function on each listener in the list, with 6 parameters. */ | |||||
template <LL_TEMPLATE(1), LL_TEMPLATE(2), LL_TEMPLATE(3), LL_TEMPLATE(4), LL_TEMPLATE(5), LL_TEMPLATE(6)> | |||||
void call (void (ListenerClass::*callbackFunction) (P1, P2, P3, P4, P5, P6), | |||||
LL_PARAM(1), LL_PARAM(2), LL_PARAM(3), LL_PARAM(4), LL_PARAM(5), LL_PARAM(6)) | |||||
{ | |||||
for (Iterator<DummyBailOutChecker, ThisType> iter (*this); iter.next();) | |||||
(iter.getListener()->*callbackFunction) (param1, param2, param3, param4, param5, param6); | |||||
} | |||||
/** Calls a member function, with 6 parameters, on all but the specified listener in the list. | |||||
This can be useful if the caller is also a listener and needs to exclude itself. | |||||
*/ | |||||
template <LL_TEMPLATE(1), LL_TEMPLATE(2), LL_TEMPLATE(3), LL_TEMPLATE(4), LL_TEMPLATE(5), LL_TEMPLATE(6)> | |||||
void callExcluding (ListenerClass* listenerToExclude, | |||||
void (ListenerClass::*callbackFunction) (P1, P2, P3, P4, P5, P6), | |||||
LL_PARAM(1), LL_PARAM(2), LL_PARAM(3), LL_PARAM(4), LL_PARAM(5), LL_PARAM(6)) | |||||
{ | |||||
for (Iterator<DummyBailOutChecker, ThisType> iter (*this); iter.next();) | |||||
if (iter.getListener() != listenerToExclude) | |||||
(iter.getListener()->*callbackFunction) (param1, param2, param3, param4, param5, param6); | |||||
} | |||||
/** Calls a member function on each listener in the list, with 6 parameters and a bail-out-checker. | |||||
See the class description for info about writing a bail-out checker. | |||||
*/ | |||||
template <class BailOutCheckerType, LL_TEMPLATE(1), LL_TEMPLATE(2), LL_TEMPLATE(3), LL_TEMPLATE(4), LL_TEMPLATE(5), LL_TEMPLATE(6)> | |||||
void callChecked (const BailOutCheckerType& bailOutChecker, | |||||
void (ListenerClass::*callbackFunction) (P1, P2, P3, P4, P5, P6), | |||||
LL_PARAM(1), LL_PARAM(2), LL_PARAM(3), LL_PARAM(4), LL_PARAM(5), LL_PARAM(6)) | |||||
{ | |||||
for (Iterator<BailOutCheckerType, ThisType> iter (*this); iter.next (bailOutChecker);) | |||||
(iter.getListener()->*callbackFunction) (param1, param2, param3, param4, param5, param6); | |||||
} | |||||
/** Calls a member function, with 5 parameters, on all but the specified listener in the list | |||||
with a bail-out-checker. This can be useful if the caller is also a listener and needs to | |||||
exclude itself. See the class description for info about writing a bail-out checker. | |||||
*/ | |||||
template <class BailOutCheckerType, LL_TEMPLATE(1), LL_TEMPLATE(2), LL_TEMPLATE(3), LL_TEMPLATE(4), LL_TEMPLATE(5), LL_TEMPLATE(6)> | |||||
void callCheckedExcluding (ListenerClass* listenerToExclude, | |||||
const BailOutCheckerType& bailOutChecker, | |||||
void (ListenerClass::*callbackFunction) (P1, P2, P3, P4, P5, P6), | |||||
LL_PARAM(1), LL_PARAM(2), LL_PARAM(3), LL_PARAM(4), LL_PARAM(5), LL_PARAM(6)) | |||||
{ | |||||
for (Iterator<BailOutCheckerType, ThisType> iter (*this); iter.next (bailOutChecker);) | |||||
if (iter.getListener() != listenerToExclude) | |||||
(iter.getListener()->*callbackFunction) (param1, param2, param3, param4, param5, param6); | |||||
} | |||||
//============================================================================== | |||||
/** A dummy bail-out checker that always returns false. | |||||
See the ListenerList notes for more info about bail-out checkers. | |||||
*/ | |||||
struct DummyBailOutChecker | |||||
{ | |||||
bool shouldBailOut() const noexcept { return false; } | |||||
}; | |||||
//============================================================================== | |||||
/** Iterates the listeners in a ListenerList. */ | |||||
template <class BailOutCheckerType, class ListType> | |||||
class Iterator | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
Iterator (const ListType& listToIterate) noexcept | |||||
: list (listToIterate), index (listToIterate.size()) | |||||
{} | |||||
~Iterator() noexcept {} | |||||
//============================================================================== | |||||
bool next() noexcept | |||||
{ | |||||
if (index <= 0) | |||||
return false; | |||||
const int listSize = list.size(); | |||||
if (--index < listSize) | |||||
return true; | |||||
index = listSize - 1; | |||||
return index >= 0; | |||||
} | |||||
bool next (const BailOutCheckerType& bailOutChecker) noexcept | |||||
{ | |||||
return (! bailOutChecker.shouldBailOut()) && next(); | |||||
} | |||||
typename ListType::ListenerType* getListener() const noexcept | |||||
{ | |||||
return list.getListeners().getUnchecked (index); | |||||
} | |||||
//============================================================================== | |||||
private: | |||||
const ListType& list; | |||||
int index; | |||||
JUCE_DECLARE_NON_COPYABLE (Iterator) | |||||
}; | |||||
typedef ListenerList<ListenerClass, ArrayType> ThisType; | |||||
typedef ListenerClass ListenerType; | |||||
const ArrayType& getListeners() const noexcept { return listeners; } | |||||
private: | |||||
//============================================================================== | |||||
ArrayType listeners; | |||||
JUCE_DECLARE_NON_COPYABLE (ListenerList) | |||||
#undef LL_TEMPLATE | |||||
#undef LL_PARAM | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,247 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
NamedValueSet::NamedValueSet() noexcept | |||||
{ | |||||
} | |||||
NamedValueSet::NamedValueSet (const NamedValueSet& other) | |||||
: values (other.values) | |||||
{ | |||||
} | |||||
NamedValueSet& NamedValueSet::operator= (const NamedValueSet& other) | |||||
{ | |||||
clear(); | |||||
values = other.values; | |||||
return *this; | |||||
} | |||||
NamedValueSet::NamedValueSet (NamedValueSet&& other) noexcept | |||||
: values (static_cast<Array<NamedValue>&&> (other.values)) | |||||
{ | |||||
} | |||||
NamedValueSet& NamedValueSet::operator= (NamedValueSet&& other) noexcept | |||||
{ | |||||
other.values.swapWith (values); | |||||
return *this; | |||||
} | |||||
NamedValueSet::~NamedValueSet() noexcept | |||||
{ | |||||
} | |||||
void NamedValueSet::clear() | |||||
{ | |||||
values.clear(); | |||||
} | |||||
bool NamedValueSet::operator== (const NamedValueSet& other) const | |||||
{ | |||||
return values == other.values; | |||||
} | |||||
bool NamedValueSet::operator!= (const NamedValueSet& other) const | |||||
{ | |||||
return ! operator== (other); | |||||
} | |||||
int NamedValueSet::size() const noexcept | |||||
{ | |||||
return values.size(); | |||||
} | |||||
bool NamedValueSet::isEmpty() const noexcept | |||||
{ | |||||
return values.isEmpty(); | |||||
} | |||||
static const var& getNullVarRef() noexcept | |||||
{ | |||||
#if JUCE_ALLOW_STATIC_NULL_VARIABLES | |||||
return var::null; | |||||
#else | |||||
static var nullVar; | |||||
return nullVar; | |||||
#endif | |||||
} | |||||
const var& NamedValueSet::operator[] (const Identifier& name) const noexcept | |||||
{ | |||||
if (const var* v = getVarPointer (name)) | |||||
return *v; | |||||
return getNullVarRef(); | |||||
} | |||||
var NamedValueSet::getWithDefault (const Identifier& name, const var& defaultReturnValue) const | |||||
{ | |||||
if (const var* const v = getVarPointer (name)) | |||||
return *v; | |||||
return defaultReturnValue; | |||||
} | |||||
var* NamedValueSet::getVarPointer (const Identifier& name) const noexcept | |||||
{ | |||||
for (NamedValue* e = values.end(), *i = values.begin(); i != e; ++i) | |||||
if (i->name == name) | |||||
return &(i->value); | |||||
return nullptr; | |||||
} | |||||
bool NamedValueSet::set (const Identifier& name, var&& newValue) | |||||
{ | |||||
if (var* const v = getVarPointer (name)) | |||||
{ | |||||
if (v->equalsWithSameType (newValue)) | |||||
return false; | |||||
*v = static_cast<var&&> (newValue); | |||||
return true; | |||||
} | |||||
values.add (NamedValue (name, static_cast<var&&> (newValue))); | |||||
return true; | |||||
} | |||||
bool NamedValueSet::set (const Identifier& name, const var& newValue) | |||||
{ | |||||
if (var* const v = getVarPointer (name)) | |||||
{ | |||||
if (v->equalsWithSameType (newValue)) | |||||
return false; | |||||
*v = newValue; | |||||
return true; | |||||
} | |||||
values.add (NamedValue (name, newValue)); | |||||
return true; | |||||
} | |||||
bool NamedValueSet::contains (const Identifier& name) const noexcept | |||||
{ | |||||
return getVarPointer (name) != nullptr; | |||||
} | |||||
int NamedValueSet::indexOf (const Identifier& name) const noexcept | |||||
{ | |||||
const int numValues = values.size(); | |||||
for (int i = 0; i < numValues; ++i) | |||||
if (values.getReference(i).name == name) | |||||
return i; | |||||
return -1; | |||||
} | |||||
bool NamedValueSet::remove (const Identifier& name) | |||||
{ | |||||
const int numValues = values.size(); | |||||
for (int i = 0; i < numValues; ++i) | |||||
{ | |||||
if (values.getReference(i).name == name) | |||||
{ | |||||
values.remove (i); | |||||
return true; | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
Identifier NamedValueSet::getName (const int index) const noexcept | |||||
{ | |||||
if (isPositiveAndBelow (index, values.size())) | |||||
return values.getReference (index).name; | |||||
jassertfalse; | |||||
return Identifier(); | |||||
} | |||||
const var& NamedValueSet::getValueAt (const int index) const noexcept | |||||
{ | |||||
if (isPositiveAndBelow (index, values.size())) | |||||
return values.getReference (index).value; | |||||
jassertfalse; | |||||
return getNullVarRef(); | |||||
} | |||||
var* NamedValueSet::getVarPointerAt (int index) const noexcept | |||||
{ | |||||
if (isPositiveAndBelow (index, values.size())) | |||||
return &(values.getReference (index).value); | |||||
return nullptr; | |||||
} | |||||
void NamedValueSet::setFromXmlAttributes (const XmlElement& xml) | |||||
{ | |||||
values.clearQuick(); | |||||
for (const XmlElement::XmlAttributeNode* att = xml.attributes; att != nullptr; att = att->nextListItem) | |||||
{ | |||||
if (att->name.toString().startsWith ("base64:")) | |||||
{ | |||||
MemoryBlock mb; | |||||
if (mb.fromBase64Encoding (att->value)) | |||||
{ | |||||
values.add (NamedValue (att->name.toString().substring (7), var (mb))); | |||||
continue; | |||||
} | |||||
} | |||||
values.add (NamedValue (att->name, var (att->value))); | |||||
} | |||||
} | |||||
void NamedValueSet::copyToXmlAttributes (XmlElement& xml) const | |||||
{ | |||||
for (NamedValue* e = values.end(), *i = values.begin(); i != e; ++i) | |||||
{ | |||||
if (const MemoryBlock* mb = i->value.getBinaryData()) | |||||
{ | |||||
xml.setAttribute ("base64:" + i->name.toString(), mb->toBase64Encoding()); | |||||
} | |||||
else | |||||
{ | |||||
// These types can't be stored as XML! | |||||
jassert (! i->value.isObject()); | |||||
jassert (! i->value.isMethod()); | |||||
jassert (! i->value.isArray()); | |||||
xml.setAttribute (i->name.toString(), | |||||
i->value.toString()); | |||||
} | |||||
} | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,175 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** Holds a set of named var objects. | |||||
This can be used as a basic structure to hold a set of var object, which can | |||||
be retrieved by using their identifier. | |||||
*/ | |||||
class JUCE_API NamedValueSet | |||||
{ | |||||
public: | |||||
/** Creates an empty set. */ | |||||
NamedValueSet() noexcept; | |||||
/** Creates a copy of another set. */ | |||||
NamedValueSet (const NamedValueSet&); | |||||
/** Replaces this set with a copy of another set. */ | |||||
NamedValueSet& operator= (const NamedValueSet&); | |||||
/** Move constructor */ | |||||
NamedValueSet (NamedValueSet&&) noexcept; | |||||
/** Move assignment operator */ | |||||
NamedValueSet& operator= (NamedValueSet&&) noexcept; | |||||
/** Destructor. */ | |||||
~NamedValueSet() noexcept; | |||||
bool operator== (const NamedValueSet&) const; | |||||
bool operator!= (const NamedValueSet&) const; | |||||
//============================================================================== | |||||
struct NamedValue | |||||
{ | |||||
NamedValue() noexcept {} | |||||
NamedValue (const Identifier& n, const var& v) : name (n), value (v) {} | |||||
NamedValue (const NamedValue& other) : name (other.name), value (other.value) {} | |||||
NamedValue (NamedValue&& other) noexcept | |||||
: name (static_cast<Identifier&&> (other.name)), | |||||
value (static_cast<var&&> (other.value)) | |||||
{ | |||||
} | |||||
NamedValue (Identifier&& n, var&& v) noexcept | |||||
: name (static_cast<Identifier&&> (n)), | |||||
value (static_cast<var&&> (v)) | |||||
{ | |||||
} | |||||
NamedValue& operator= (NamedValue&& other) noexcept | |||||
{ | |||||
name = static_cast<Identifier&&> (other.name); | |||||
value = static_cast<var&&> (other.value); | |||||
return *this; | |||||
} | |||||
bool operator== (const NamedValue& other) const noexcept { return name == other.name && value == other.value; } | |||||
bool operator!= (const NamedValue& other) const noexcept { return ! operator== (other); } | |||||
Identifier name; | |||||
var value; | |||||
}; | |||||
NamedValueSet::NamedValue* begin() { return values.begin(); } | |||||
NamedValueSet::NamedValue* end() { return values.end(); } | |||||
//============================================================================== | |||||
/** Returns the total number of values that the set contains. */ | |||||
int size() const noexcept; | |||||
/** Returns true if the set is empty. */ | |||||
bool isEmpty() const noexcept; | |||||
/** Returns the value of a named item. | |||||
If the name isn't found, this will return a void variant. | |||||
@see getProperty | |||||
*/ | |||||
const var& operator[] (const Identifier& name) const noexcept; | |||||
/** Tries to return the named value, but if no such value is found, this will | |||||
instead return the supplied default value. | |||||
*/ | |||||
var getWithDefault (const Identifier& name, const var& defaultReturnValue) const; | |||||
/** Changes or adds a named value. | |||||
@returns true if a value was changed or added; false if the | |||||
value was already set the value passed-in. | |||||
*/ | |||||
bool set (const Identifier& name, const var& newValue); | |||||
/** Changes or adds a named value. | |||||
@returns true if a value was changed or added; false if the | |||||
value was already set the value passed-in. | |||||
*/ | |||||
bool set (const Identifier& name, var&& newValue); | |||||
/** Returns true if the set contains an item with the specified name. */ | |||||
bool contains (const Identifier& name) const noexcept; | |||||
/** Removes a value from the set. | |||||
@returns true if a value was removed; false if there was no value | |||||
with the name that was given. | |||||
*/ | |||||
bool remove (const Identifier& name); | |||||
/** Returns the name of the value at a given index. | |||||
The index must be between 0 and size() - 1. | |||||
*/ | |||||
Identifier getName (int index) const noexcept; | |||||
/** Returns a pointer to the var that holds a named value, or null if there is | |||||
no value with this name. | |||||
Do not use this method unless you really need access to the internal var object | |||||
for some reason - for normal reading and writing always prefer operator[]() and set(). | |||||
*/ | |||||
var* getVarPointer (const Identifier& name) const noexcept; | |||||
/** Returns the value of the item at a given index. | |||||
The index must be between 0 and size() - 1. | |||||
*/ | |||||
const var& getValueAt (int index) const noexcept; | |||||
/** Returns the value of the item at a given index. | |||||
The index must be between 0 and size() - 1, or this will return a nullptr | |||||
*/ | |||||
var* getVarPointerAt (int index) const noexcept; | |||||
/** Returns the index of the given name, or -1 if it's not found. */ | |||||
int indexOf (const Identifier& name) const noexcept; | |||||
/** Removes all values. */ | |||||
void clear(); | |||||
//============================================================================== | |||||
/** Sets properties to the values of all of an XML element's attributes. */ | |||||
void setFromXmlAttributes (const XmlElement& xml); | |||||
/** Sets attributes in an XML element corresponding to each of this object's | |||||
properties. | |||||
*/ | |||||
void copyToXmlAttributes (XmlElement& xml) const; | |||||
private: | |||||
//============================================================================== | |||||
Array<NamedValue> values; | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,896 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** An array designed for holding objects. | |||||
This holds a list of pointers to objects, and will automatically | |||||
delete the objects when they are removed from the array, or when the | |||||
array is itself deleted. | |||||
Declare it in the form: OwnedArray<MyObjectClass> | |||||
..and then add new objects, e.g. myOwnedArray.add (new MyObjectClass()); | |||||
After adding objects, they are 'owned' by the array and will be deleted when | |||||
removed or replaced. | |||||
To make all the array's methods thread-safe, pass in "CriticalSection" as the templated | |||||
TypeOfCriticalSectionToUse parameter, instead of the default DummyCriticalSection. | |||||
@see Array, ReferenceCountedArray, StringArray, CriticalSection | |||||
*/ | |||||
template <class ObjectClass, | |||||
class TypeOfCriticalSectionToUse = DummyCriticalSection> | |||||
class OwnedArray | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an empty array. */ | |||||
OwnedArray() noexcept | |||||
: numUsed (0) | |||||
{ | |||||
} | |||||
/** Deletes the array and also deletes any objects inside it. | |||||
To get rid of the array without deleting its objects, use its | |||||
clear (false) method before deleting it. | |||||
*/ | |||||
~OwnedArray() | |||||
{ | |||||
deleteAllObjects(); | |||||
} | |||||
/** Move constructor */ | |||||
OwnedArray (OwnedArray&& other) noexcept | |||||
: data (static_cast<ArrayAllocationBase <ObjectClass*, TypeOfCriticalSectionToUse>&&> (other.data)), | |||||
numUsed (other.numUsed) | |||||
{ | |||||
other.numUsed = 0; | |||||
} | |||||
/** Move assignment operator */ | |||||
OwnedArray& operator= (OwnedArray&& other) noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
deleteAllObjects(); | |||||
data = static_cast<ArrayAllocationBase <ObjectClass*, TypeOfCriticalSectionToUse>&&> (other.data); | |||||
numUsed = other.numUsed; | |||||
other.numUsed = 0; | |||||
return *this; | |||||
} | |||||
//============================================================================== | |||||
/** Clears the array, optionally deleting the objects inside it first. */ | |||||
void clear (bool deleteObjects = true) | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
if (deleteObjects) | |||||
deleteAllObjects(); | |||||
data.setAllocatedSize (0); | |||||
numUsed = 0; | |||||
} | |||||
//============================================================================== | |||||
/** Clears the array, optionally deleting the objects inside it first. */ | |||||
void clearQuick (bool deleteObjects) | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
if (deleteObjects) | |||||
deleteAllObjects(); | |||||
numUsed = 0; | |||||
} | |||||
//============================================================================== | |||||
/** Returns the number of items currently in the array. | |||||
@see operator[] | |||||
*/ | |||||
inline int size() const noexcept | |||||
{ | |||||
return numUsed; | |||||
} | |||||
/** Returns true if the array is empty, false otherwise. */ | |||||
inline bool isEmpty() const noexcept | |||||
{ | |||||
return size() == 0; | |||||
} | |||||
/** Returns a pointer to the object at this index in the array. | |||||
If the index is out-of-range, this will return a null pointer, (and | |||||
it could be null anyway, because it's ok for the array to hold null | |||||
pointers as well as objects). | |||||
@see getUnchecked | |||||
*/ | |||||
inline ObjectClass* operator[] (const int index) const noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
if (isPositiveAndBelow (index, numUsed)) | |||||
{ | |||||
jassert (data.elements != nullptr); | |||||
return data.elements [index]; | |||||
} | |||||
return nullptr; | |||||
} | |||||
/** Returns a pointer to the object at this index in the array, without checking whether the index is in-range. | |||||
This is a faster and less safe version of operator[] which doesn't check the index passed in, so | |||||
it can be used when you're sure the index is always going to be legal. | |||||
*/ | |||||
inline ObjectClass* getUnchecked (const int index) const noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
jassert (isPositiveAndBelow (index, numUsed) && data.elements != nullptr); | |||||
return data.elements [index]; | |||||
} | |||||
/** Returns a pointer to the first object in the array. | |||||
This will return a null pointer if the array's empty. | |||||
@see getLast | |||||
*/ | |||||
inline ObjectClass* getFirst() const noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
if (numUsed > 0) | |||||
{ | |||||
jassert (data.elements != nullptr); | |||||
return data.elements [0]; | |||||
} | |||||
return nullptr; | |||||
} | |||||
/** Returns a pointer to the last object in the array. | |||||
This will return a null pointer if the array's empty. | |||||
@see getFirst | |||||
*/ | |||||
inline ObjectClass* getLast() const noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
if (numUsed > 0) | |||||
{ | |||||
jassert (data.elements != nullptr); | |||||
return data.elements [numUsed - 1]; | |||||
} | |||||
return nullptr; | |||||
} | |||||
/** Returns a pointer to the actual array data. | |||||
This pointer will only be valid until the next time a non-const method | |||||
is called on the array. | |||||
*/ | |||||
inline ObjectClass** getRawDataPointer() noexcept | |||||
{ | |||||
return data.elements; | |||||
} | |||||
//============================================================================== | |||||
/** Returns a pointer to the first element in the array. | |||||
This method is provided for compatibility with standard C++ iteration mechanisms. | |||||
*/ | |||||
inline ObjectClass** begin() const noexcept | |||||
{ | |||||
return data.elements; | |||||
} | |||||
/** Returns a pointer to the element which follows the last element in the array. | |||||
This method is provided for compatibility with standard C++ iteration mechanisms. | |||||
*/ | |||||
inline ObjectClass** end() const noexcept | |||||
{ | |||||
#if JUCE_DEBUG | |||||
if (data.elements == nullptr || numUsed <= 0) // (to keep static analysers happy) | |||||
return data.elements; | |||||
#endif | |||||
return data.elements + numUsed; | |||||
} | |||||
//============================================================================== | |||||
/** Finds the index of an object which might be in the array. | |||||
@param objectToLookFor the object to look for | |||||
@returns the index at which the object was found, or -1 if it's not found | |||||
*/ | |||||
int indexOf (const ObjectClass* objectToLookFor) const noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
ObjectClass* const* e = data.elements.get(); | |||||
ObjectClass* const* const end_ = e + numUsed; | |||||
for (; e != end_; ++e) | |||||
if (objectToLookFor == *e) | |||||
return static_cast<int> (e - data.elements.get()); | |||||
return -1; | |||||
} | |||||
/** Returns true if the array contains a specified object. | |||||
@param objectToLookFor the object to look for | |||||
@returns true if the object is in the array | |||||
*/ | |||||
bool contains (const ObjectClass* objectToLookFor) const noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
ObjectClass* const* e = data.elements.get(); | |||||
ObjectClass* const* const end_ = e + numUsed; | |||||
for (; e != end_; ++e) | |||||
if (objectToLookFor == *e) | |||||
return true; | |||||
return false; | |||||
} | |||||
//============================================================================== | |||||
/** Appends a new object to the end of the array. | |||||
Note that the this object will be deleted by the OwnedArray when it | |||||
is removed, so be careful not to delete it somewhere else. | |||||
Also be careful not to add the same object to the array more than once, | |||||
as this will obviously cause deletion of dangling pointers. | |||||
@param newObject the new object to add to the array | |||||
@returns the new object that was added | |||||
@see set, insert, addIfNotAlreadyThere, addSorted | |||||
*/ | |||||
ObjectClass* add (ObjectClass* newObject) noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
data.ensureAllocatedSize (numUsed + 1); | |||||
jassert (data.elements != nullptr); | |||||
data.elements [numUsed++] = newObject; | |||||
return newObject; | |||||
} | |||||
/** Inserts a new object into the array at the given index. | |||||
Note that the this object will be deleted by the OwnedArray when it | |||||
is removed, so be careful not to delete it somewhere else. | |||||
If the index is less than 0 or greater than the size of the array, the | |||||
element will be added to the end of the array. | |||||
Otherwise, it will be inserted into the array, moving all the later elements | |||||
along to make room. | |||||
Be careful not to add the same object to the array more than once, | |||||
as this will obviously cause deletion of dangling pointers. | |||||
@param indexToInsertAt the index at which the new element should be inserted | |||||
@param newObject the new object to add to the array | |||||
@returns the new object that was added | |||||
@see add, addSorted, addIfNotAlreadyThere, set | |||||
*/ | |||||
ObjectClass* insert (int indexToInsertAt, ObjectClass* newObject) noexcept | |||||
{ | |||||
if (indexToInsertAt < 0) | |||||
return add (newObject); | |||||
const ScopedLockType lock (getLock()); | |||||
if (indexToInsertAt > numUsed) | |||||
indexToInsertAt = numUsed; | |||||
data.ensureAllocatedSize (numUsed + 1); | |||||
jassert (data.elements != nullptr); | |||||
ObjectClass** const e = data.elements + indexToInsertAt; | |||||
const int numToMove = numUsed - indexToInsertAt; | |||||
if (numToMove > 0) | |||||
memmove (e + 1, e, sizeof (ObjectClass*) * (size_t) numToMove); | |||||
*e = newObject; | |||||
++numUsed; | |||||
return newObject; | |||||
} | |||||
/** Inserts an array of values into this array at a given position. | |||||
If the index is less than 0 or greater than the size of the array, the | |||||
new elements will be added to the end of the array. | |||||
Otherwise, they will be inserted into the array, moving all the later elements | |||||
along to make room. | |||||
@param indexToInsertAt the index at which the first new element should be inserted | |||||
@param newObjects the new values to add to the array | |||||
@param numberOfElements how many items are in the array | |||||
@see insert, add, addSorted, set | |||||
*/ | |||||
void insertArray (int indexToInsertAt, | |||||
ObjectClass* const* newObjects, | |||||
int numberOfElements) | |||||
{ | |||||
if (numberOfElements > 0) | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
data.ensureAllocatedSize (numUsed + numberOfElements); | |||||
ObjectClass** insertPos = data.elements; | |||||
if (isPositiveAndBelow (indexToInsertAt, numUsed)) | |||||
{ | |||||
insertPos += indexToInsertAt; | |||||
const size_t numberToMove = (size_t) (numUsed - indexToInsertAt); | |||||
memmove (insertPos + numberOfElements, insertPos, numberToMove * sizeof (ObjectClass*)); | |||||
} | |||||
else | |||||
{ | |||||
insertPos += numUsed; | |||||
} | |||||
numUsed += numberOfElements; | |||||
while (--numberOfElements >= 0) | |||||
*insertPos++ = *newObjects++; | |||||
} | |||||
} | |||||
/** Appends a new object at the end of the array as long as the array doesn't | |||||
already contain it. | |||||
If the array already contains a matching object, nothing will be done. | |||||
@param newObject the new object to add to the array | |||||
@returns true if the new object was added, false otherwise | |||||
*/ | |||||
bool addIfNotAlreadyThere (ObjectClass* newObject) noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
if (contains (newObject)) | |||||
return false; | |||||
add (newObject); | |||||
return true; | |||||
} | |||||
/** Replaces an object in the array with a different one. | |||||
If the index is less than zero, this method does nothing. | |||||
If the index is beyond the end of the array, the new object is added to the end of the array. | |||||
Be careful not to add the same object to the array more than once, | |||||
as this will obviously cause deletion of dangling pointers. | |||||
@param indexToChange the index whose value you want to change | |||||
@param newObject the new value to set for this index. | |||||
@param deleteOldElement whether to delete the object that's being replaced with the new one | |||||
@see add, insert, remove | |||||
*/ | |||||
ObjectClass* set (int indexToChange, ObjectClass* newObject, bool deleteOldElement = true) | |||||
{ | |||||
if (indexToChange >= 0) | |||||
{ | |||||
ScopedPointer<ObjectClass> toDelete; | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
if (indexToChange < numUsed) | |||||
{ | |||||
if (deleteOldElement) | |||||
{ | |||||
toDelete = data.elements [indexToChange]; | |||||
if (toDelete == newObject) | |||||
toDelete.release(); | |||||
} | |||||
data.elements [indexToChange] = newObject; | |||||
} | |||||
else | |||||
{ | |||||
data.ensureAllocatedSize (numUsed + 1); | |||||
data.elements [numUsed++] = newObject; | |||||
} | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
jassertfalse; // you're trying to set an object at a negative index, which doesn't have | |||||
// any effect - but since the object is not being added, it may be leaking.. | |||||
} | |||||
return newObject; | |||||
} | |||||
/** Adds elements from another array to the end of this array. | |||||
@param arrayToAddFrom the array from which to copy the elements | |||||
@param startIndex the first element of the other array to start copying from | |||||
@param numElementsToAdd how many elements to add from the other array. If this | |||||
value is negative or greater than the number of available elements, | |||||
all available elements will be copied. | |||||
@see add | |||||
*/ | |||||
template <class OtherArrayType> | |||||
void addArray (const OtherArrayType& arrayToAddFrom, | |||||
int startIndex = 0, | |||||
int numElementsToAdd = -1) | |||||
{ | |||||
const typename OtherArrayType::ScopedLockType lock1 (arrayToAddFrom.getLock()); | |||||
const ScopedLockType lock2 (getLock()); | |||||
if (startIndex < 0) | |||||
{ | |||||
jassertfalse; | |||||
startIndex = 0; | |||||
} | |||||
if (numElementsToAdd < 0 || startIndex + numElementsToAdd > arrayToAddFrom.size()) | |||||
numElementsToAdd = arrayToAddFrom.size() - startIndex; | |||||
data.ensureAllocatedSize (numUsed + numElementsToAdd); | |||||
jassert (numElementsToAdd <= 0 || data.elements != nullptr); | |||||
while (--numElementsToAdd >= 0) | |||||
{ | |||||
data.elements [numUsed] = arrayToAddFrom.getUnchecked (startIndex++); | |||||
++numUsed; | |||||
} | |||||
} | |||||
/** Adds copies of the elements in another array to the end of this array. | |||||
The other array must be either an OwnedArray of a compatible type of object, or an Array | |||||
containing pointers to the same kind of object. The objects involved must provide | |||||
a copy constructor, and this will be used to create new copies of each element, and | |||||
add them to this array. | |||||
@param arrayToAddFrom the array from which to copy the elements | |||||
@param startIndex the first element of the other array to start copying from | |||||
@param numElementsToAdd how many elements to add from the other array. If this | |||||
value is negative or greater than the number of available elements, | |||||
all available elements will be copied. | |||||
@see add | |||||
*/ | |||||
template <class OtherArrayType> | |||||
void addCopiesOf (const OtherArrayType& arrayToAddFrom, | |||||
int startIndex = 0, | |||||
int numElementsToAdd = -1) | |||||
{ | |||||
const typename OtherArrayType::ScopedLockType lock1 (arrayToAddFrom.getLock()); | |||||
const ScopedLockType lock2 (getLock()); | |||||
if (startIndex < 0) | |||||
{ | |||||
jassertfalse; | |||||
startIndex = 0; | |||||
} | |||||
if (numElementsToAdd < 0 || startIndex + numElementsToAdd > arrayToAddFrom.size()) | |||||
numElementsToAdd = arrayToAddFrom.size() - startIndex; | |||||
data.ensureAllocatedSize (numUsed + numElementsToAdd); | |||||
jassert (numElementsToAdd <= 0 || data.elements != nullptr); | |||||
while (--numElementsToAdd >= 0) | |||||
data.elements [numUsed++] = createCopyIfNotNull (arrayToAddFrom.getUnchecked (startIndex++)); | |||||
} | |||||
/** Inserts a new object into the array assuming that the array is sorted. | |||||
This will use a comparator to find the position at which the new object | |||||
should go. If the array isn't sorted, the behaviour of this | |||||
method will be unpredictable. | |||||
@param comparator the comparator to use to compare the elements - see the sort method | |||||
for details about this object's structure | |||||
@param newObject the new object to insert to the array | |||||
@returns the index at which the new object was added | |||||
@see add, sort, indexOfSorted | |||||
*/ | |||||
template <class ElementComparator> | |||||
int addSorted (ElementComparator& comparator, ObjectClass* const newObject) noexcept | |||||
{ | |||||
ignoreUnused (comparator); // if you pass in an object with a static compareElements() method, this | |||||
// avoids getting warning messages about the parameter being unused | |||||
const ScopedLockType lock (getLock()); | |||||
const int index = findInsertIndexInSortedArray (comparator, data.elements.get(), newObject, 0, numUsed); | |||||
insert (index, newObject); | |||||
return index; | |||||
} | |||||
/** Finds the index of an object in the array, assuming that the array is sorted. | |||||
This will use a comparator to do a binary-chop to find the index of the given | |||||
element, if it exists. If the array isn't sorted, the behaviour of this | |||||
method will be unpredictable. | |||||
@param comparator the comparator to use to compare the elements - see the sort() | |||||
method for details about the form this object should take | |||||
@param objectToLookFor the object to search for | |||||
@returns the index of the element, or -1 if it's not found | |||||
@see addSorted, sort | |||||
*/ | |||||
template <typename ElementComparator> | |||||
int indexOfSorted (ElementComparator& comparator, const ObjectClass* const objectToLookFor) const noexcept | |||||
{ | |||||
ignoreUnused (comparator); | |||||
const ScopedLockType lock (getLock()); | |||||
int s = 0, e = numUsed; | |||||
while (s < e) | |||||
{ | |||||
if (comparator.compareElements (objectToLookFor, data.elements [s]) == 0) | |||||
return s; | |||||
const int halfway = (s + e) / 2; | |||||
if (halfway == s) | |||||
break; | |||||
if (comparator.compareElements (objectToLookFor, data.elements [halfway]) >= 0) | |||||
s = halfway; | |||||
else | |||||
e = halfway; | |||||
} | |||||
return -1; | |||||
} | |||||
//============================================================================== | |||||
/** Removes an object from the array. | |||||
This will remove the object at a given index (optionally also | |||||
deleting it) and move back all the subsequent objects to close the gap. | |||||
If the index passed in is out-of-range, nothing will happen. | |||||
@param indexToRemove the index of the element to remove | |||||
@param deleteObject whether to delete the object that is removed | |||||
@see removeObject, removeRange | |||||
*/ | |||||
void remove (int indexToRemove, bool deleteObject = true) | |||||
{ | |||||
ScopedPointer<ObjectClass> toDelete; | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
if (isPositiveAndBelow (indexToRemove, numUsed)) | |||||
{ | |||||
ObjectClass** const e = data.elements + indexToRemove; | |||||
if (deleteObject) | |||||
toDelete = *e; | |||||
--numUsed; | |||||
const int numToShift = numUsed - indexToRemove; | |||||
if (numToShift > 0) | |||||
memmove (e, e + 1, sizeof (ObjectClass*) * (size_t) numToShift); | |||||
} | |||||
} | |||||
if ((numUsed << 1) < data.numAllocated) | |||||
minimiseStorageOverheads(); | |||||
} | |||||
/** Removes and returns an object from the array without deleting it. | |||||
This will remove the object at a given index and return it, moving back all | |||||
the subsequent objects to close the gap. If the index passed in is out-of-range, | |||||
nothing will happen. | |||||
@param indexToRemove the index of the element to remove | |||||
@see remove, removeObject, removeRange | |||||
*/ | |||||
ObjectClass* removeAndReturn (int indexToRemove) | |||||
{ | |||||
ObjectClass* removedItem = nullptr; | |||||
const ScopedLockType lock (getLock()); | |||||
if (isPositiveAndBelow (indexToRemove, numUsed)) | |||||
{ | |||||
ObjectClass** const e = data.elements + indexToRemove; | |||||
removedItem = *e; | |||||
--numUsed; | |||||
const int numToShift = numUsed - indexToRemove; | |||||
if (numToShift > 0) | |||||
memmove (e, e + 1, sizeof (ObjectClass*) * (size_t) numToShift); | |||||
if ((numUsed << 1) < data.numAllocated) | |||||
minimiseStorageOverheads(); | |||||
} | |||||
return removedItem; | |||||
} | |||||
/** Removes a specified object from the array. | |||||
If the item isn't found, no action is taken. | |||||
@param objectToRemove the object to try to remove | |||||
@param deleteObject whether to delete the object (if it's found) | |||||
@see remove, removeRange | |||||
*/ | |||||
void removeObject (const ObjectClass* objectToRemove, bool deleteObject = true) | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
ObjectClass** const e = data.elements.get(); | |||||
for (int i = 0; i < numUsed; ++i) | |||||
{ | |||||
if (objectToRemove == e[i]) | |||||
{ | |||||
remove (i, deleteObject); | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
/** Removes a range of objects from the array. | |||||
This will remove a set of objects, starting from the given index, | |||||
and move any subsequent elements down to close the gap. | |||||
If the range extends beyond the bounds of the array, it will | |||||
be safely clipped to the size of the array. | |||||
@param startIndex the index of the first object to remove | |||||
@param numberToRemove how many objects should be removed | |||||
@param deleteObjects whether to delete the objects that get removed | |||||
@see remove, removeObject | |||||
*/ | |||||
void removeRange (int startIndex, int numberToRemove, bool deleteObjects = true) | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
const int endIndex = jlimit (0, numUsed, startIndex + numberToRemove); | |||||
startIndex = jlimit (0, numUsed, startIndex); | |||||
if (endIndex > startIndex) | |||||
{ | |||||
if (deleteObjects) | |||||
{ | |||||
for (int i = startIndex; i < endIndex; ++i) | |||||
{ | |||||
ContainerDeletePolicy<ObjectClass>::destroy (data.elements [i]); | |||||
data.elements [i] = nullptr; // (in case one of the destructors accesses this array and hits a dangling pointer) | |||||
} | |||||
} | |||||
const int rangeSize = endIndex - startIndex; | |||||
ObjectClass** e = data.elements + startIndex; | |||||
int numToShift = numUsed - endIndex; | |||||
numUsed -= rangeSize; | |||||
while (--numToShift >= 0) | |||||
{ | |||||
*e = e [rangeSize]; | |||||
++e; | |||||
} | |||||
if ((numUsed << 1) < data.numAllocated) | |||||
minimiseStorageOverheads(); | |||||
} | |||||
} | |||||
/** Removes the last n objects from the array. | |||||
@param howManyToRemove how many objects to remove from the end of the array | |||||
@param deleteObjects whether to also delete the objects that are removed | |||||
@see remove, removeObject, removeRange | |||||
*/ | |||||
void removeLast (int howManyToRemove = 1, | |||||
bool deleteObjects = true) | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
if (howManyToRemove >= numUsed) | |||||
clear (deleteObjects); | |||||
else | |||||
removeRange (numUsed - howManyToRemove, howManyToRemove, deleteObjects); | |||||
} | |||||
/** Swaps a pair of objects in the array. | |||||
If either of the indexes passed in is out-of-range, nothing will happen, | |||||
otherwise the two objects at these positions will be exchanged. | |||||
*/ | |||||
void swap (int index1, | |||||
int index2) noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
if (isPositiveAndBelow (index1, numUsed) | |||||
&& isPositiveAndBelow (index2, numUsed)) | |||||
{ | |||||
std::swap (data.elements [index1], | |||||
data.elements [index2]); | |||||
} | |||||
} | |||||
/** Moves one of the objects to a different position. | |||||
This will move the object to a specified index, shuffling along | |||||
any intervening elements as required. | |||||
So for example, if you have the array { 0, 1, 2, 3, 4, 5 } then calling | |||||
move (2, 4) would result in { 0, 1, 3, 4, 2, 5 }. | |||||
@param currentIndex the index of the object to be moved. If this isn't a | |||||
valid index, then nothing will be done | |||||
@param newIndex the index at which you'd like this object to end up. If this | |||||
is less than zero, it will be moved to the end of the array | |||||
*/ | |||||
void move (int currentIndex, int newIndex) noexcept | |||||
{ | |||||
if (currentIndex != newIndex) | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
if (isPositiveAndBelow (currentIndex, numUsed)) | |||||
{ | |||||
if (! isPositiveAndBelow (newIndex, numUsed)) | |||||
newIndex = numUsed - 1; | |||||
ObjectClass* const value = data.elements [currentIndex]; | |||||
if (newIndex > currentIndex) | |||||
{ | |||||
memmove (data.elements + currentIndex, | |||||
data.elements + currentIndex + 1, | |||||
sizeof (ObjectClass*) * (size_t) (newIndex - currentIndex)); | |||||
} | |||||
else | |||||
{ | |||||
memmove (data.elements + newIndex + 1, | |||||
data.elements + newIndex, | |||||
sizeof (ObjectClass*) * (size_t) (currentIndex - newIndex)); | |||||
} | |||||
data.elements [newIndex] = value; | |||||
} | |||||
} | |||||
} | |||||
/** This swaps the contents of this array with those of another array. | |||||
If you need to exchange two arrays, this is vastly quicker than using copy-by-value | |||||
because it just swaps their internal pointers. | |||||
*/ | |||||
template <class OtherArrayType> | |||||
void swapWith (OtherArrayType& otherArray) noexcept | |||||
{ | |||||
const ScopedLockType lock1 (getLock()); | |||||
const typename OtherArrayType::ScopedLockType lock2 (otherArray.getLock()); | |||||
data.swapWith (otherArray.data); | |||||
std::swap (numUsed, otherArray.numUsed); | |||||
} | |||||
//============================================================================== | |||||
/** Reduces the amount of storage being used by the array. | |||||
Arrays typically allocate slightly more storage than they need, and after | |||||
removing elements, they may have quite a lot of unused space allocated. | |||||
This method will reduce the amount of allocated storage to a minimum. | |||||
*/ | |||||
void minimiseStorageOverheads() noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
data.shrinkToNoMoreThan (numUsed); | |||||
} | |||||
/** Increases the array's internal storage to hold a minimum number of elements. | |||||
Calling this before adding a large known number of elements means that | |||||
the array won't have to keep dynamically resizing itself as the elements | |||||
are added, and it'll therefore be more efficient. | |||||
*/ | |||||
void ensureStorageAllocated (const int minNumElements) noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
data.ensureAllocatedSize (minNumElements); | |||||
} | |||||
//============================================================================== | |||||
/** Sorts the elements in the array. | |||||
This will use a comparator object to sort the elements into order. The object | |||||
passed must have a method of the form: | |||||
@code | |||||
int compareElements (ElementType* first, ElementType* second); | |||||
@endcode | |||||
..and this method must return: | |||||
- a value of < 0 if the first comes before the second | |||||
- a value of 0 if the two objects are equivalent | |||||
- a value of > 0 if the second comes before the first | |||||
To improve performance, the compareElements() method can be declared as static or const. | |||||
@param comparator the comparator to use for comparing elements. | |||||
@param retainOrderOfEquivalentItems if this is true, then items | |||||
which the comparator says are equivalent will be | |||||
kept in the order in which they currently appear | |||||
in the array. This is slower to perform, but may | |||||
be important in some cases. If it's false, a faster | |||||
algorithm is used, but equivalent elements may be | |||||
rearranged. | |||||
@see sortArray, indexOfSorted | |||||
*/ | |||||
template <class ElementComparator> | |||||
void sort (ElementComparator& comparator, | |||||
bool retainOrderOfEquivalentItems = false) const noexcept | |||||
{ | |||||
ignoreUnused (comparator); // if you pass in an object with a static compareElements() method, this | |||||
// avoids getting warning messages about the parameter being unused | |||||
const ScopedLockType lock (getLock()); | |||||
sortArray (comparator, data.elements.get(), 0, size() - 1, retainOrderOfEquivalentItems); | |||||
} | |||||
//============================================================================== | |||||
/** Returns the CriticalSection that locks this array. | |||||
To lock, you can call getLock().enter() and getLock().exit(), or preferably use | |||||
an object of ScopedLockType as an RAII lock for it. | |||||
*/ | |||||
inline const TypeOfCriticalSectionToUse& getLock() const noexcept { return data; } | |||||
/** Returns the type of scoped lock to use for locking this array */ | |||||
typedef typename TypeOfCriticalSectionToUse::ScopedLockType ScopedLockType; | |||||
//============================================================================== | |||||
#ifndef DOXYGEN | |||||
// Note that the swapWithArray method has been replaced by a more flexible templated version, | |||||
// and renamed "swapWith" to be more consistent with the names used in other classes. | |||||
JUCE_DEPRECATED_WITH_BODY (void swapWithArray (OwnedArray& other) noexcept, { swapWith (other); }) | |||||
#endif | |||||
private: | |||||
//============================================================================== | |||||
ArrayAllocationBase <ObjectClass*, TypeOfCriticalSectionToUse> data; | |||||
int numUsed; | |||||
void deleteAllObjects() | |||||
{ | |||||
while (numUsed > 0) | |||||
ContainerDeletePolicy<ObjectClass>::destroy (data.elements [--numUsed]); | |||||
} | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OwnedArray) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,218 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
PropertySet::PropertySet (const bool ignoreCaseOfKeyNames) | |||||
: properties (ignoreCaseOfKeyNames), | |||||
fallbackProperties (nullptr), | |||||
ignoreCaseOfKeys (ignoreCaseOfKeyNames) | |||||
{ | |||||
} | |||||
PropertySet::PropertySet (const PropertySet& other) | |||||
: properties (other.properties), | |||||
fallbackProperties (other.fallbackProperties), | |||||
ignoreCaseOfKeys (other.ignoreCaseOfKeys) | |||||
{ | |||||
} | |||||
PropertySet& PropertySet::operator= (const PropertySet& other) | |||||
{ | |||||
properties = other.properties; | |||||
fallbackProperties = other.fallbackProperties; | |||||
ignoreCaseOfKeys = other.ignoreCaseOfKeys; | |||||
propertyChanged(); | |||||
return *this; | |||||
} | |||||
PropertySet::~PropertySet() | |||||
{ | |||||
} | |||||
void PropertySet::clear() | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
if (properties.size() > 0) | |||||
{ | |||||
properties.clear(); | |||||
propertyChanged(); | |||||
} | |||||
} | |||||
String PropertySet::getValue (StringRef keyName, const String& defaultValue) const noexcept | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); | |||||
if (index >= 0) | |||||
return properties.getAllValues() [index]; | |||||
return fallbackProperties != nullptr ? fallbackProperties->getValue (keyName, defaultValue) | |||||
: defaultValue; | |||||
} | |||||
int PropertySet::getIntValue (StringRef keyName, const int defaultValue) const noexcept | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); | |||||
if (index >= 0) | |||||
return properties.getAllValues() [index].getIntValue(); | |||||
return fallbackProperties != nullptr ? fallbackProperties->getIntValue (keyName, defaultValue) | |||||
: defaultValue; | |||||
} | |||||
double PropertySet::getDoubleValue (StringRef keyName, const double defaultValue) const noexcept | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); | |||||
if (index >= 0) | |||||
return properties.getAllValues()[index].getDoubleValue(); | |||||
return fallbackProperties != nullptr ? fallbackProperties->getDoubleValue (keyName, defaultValue) | |||||
: defaultValue; | |||||
} | |||||
bool PropertySet::getBoolValue (StringRef keyName, const bool defaultValue) const noexcept | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); | |||||
if (index >= 0) | |||||
return properties.getAllValues() [index].getIntValue() != 0; | |||||
return fallbackProperties != nullptr ? fallbackProperties->getBoolValue (keyName, defaultValue) | |||||
: defaultValue; | |||||
} | |||||
XmlElement* PropertySet::getXmlValue (StringRef keyName) const | |||||
{ | |||||
return XmlDocument::parse (getValue (keyName)); | |||||
} | |||||
void PropertySet::setValue (const String& keyName, const var& v) | |||||
{ | |||||
jassert (keyName.isNotEmpty()); // shouldn't use an empty key name! | |||||
if (keyName.isNotEmpty()) | |||||
{ | |||||
const String value (v.toString()); | |||||
const ScopedLock sl (lock); | |||||
const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); | |||||
if (index < 0 || properties.getAllValues() [index] != value) | |||||
{ | |||||
properties.set (keyName, value); | |||||
propertyChanged(); | |||||
} | |||||
} | |||||
} | |||||
void PropertySet::removeValue (StringRef keyName) | |||||
{ | |||||
if (keyName.isNotEmpty()) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); | |||||
if (index >= 0) | |||||
{ | |||||
properties.remove (keyName); | |||||
propertyChanged(); | |||||
} | |||||
} | |||||
} | |||||
void PropertySet::setValue (const String& keyName, const XmlElement* const xml) | |||||
{ | |||||
setValue (keyName, xml == nullptr ? var() | |||||
: var (xml->createDocument ("", true))); | |||||
} | |||||
bool PropertySet::containsKey (StringRef keyName) const noexcept | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
return properties.getAllKeys().contains (keyName, ignoreCaseOfKeys); | |||||
} | |||||
void PropertySet::addAllPropertiesFrom (const PropertySet& source) | |||||
{ | |||||
const ScopedLock sl (source.getLock()); | |||||
for (int i = 0; i < source.properties.size(); ++i) | |||||
setValue (source.properties.getAllKeys() [i], | |||||
source.properties.getAllValues() [i]); | |||||
} | |||||
void PropertySet::setFallbackPropertySet (PropertySet* fallbackProperties_) noexcept | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
fallbackProperties = fallbackProperties_; | |||||
} | |||||
XmlElement* PropertySet::createXml (const String& nodeName) const | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
XmlElement* const xml = new XmlElement (nodeName); | |||||
for (int i = 0; i < properties.getAllKeys().size(); ++i) | |||||
{ | |||||
XmlElement* const e = xml->createNewChildElement ("VALUE"); | |||||
e->setAttribute ("name", properties.getAllKeys()[i]); | |||||
e->setAttribute ("val", properties.getAllValues()[i]); | |||||
} | |||||
return xml; | |||||
} | |||||
void PropertySet::restoreFromXml (const XmlElement& xml) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
clear(); | |||||
forEachXmlChildElementWithTagName (xml, e, "VALUE") | |||||
{ | |||||
if (e->hasAttribute ("name") | |||||
&& e->hasAttribute ("val")) | |||||
{ | |||||
properties.set (e->getStringAttribute ("name"), | |||||
e->getStringAttribute ("val")); | |||||
} | |||||
} | |||||
if (properties.size() > 0) | |||||
propertyChanged(); | |||||
} | |||||
void PropertySet::propertyChanged() | |||||
{ | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,203 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
A set of named property values, which can be strings, integers, floating point, etc. | |||||
Effectively, this just wraps a StringPairArray in an interface that makes it easier | |||||
to load and save types other than strings. | |||||
See the PropertiesFile class for a subclass of this, which automatically broadcasts change | |||||
messages and saves/loads the list from a file. | |||||
*/ | |||||
class JUCE_API PropertySet | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an empty PropertySet. | |||||
@param ignoreCaseOfKeyNames if true, the names of properties are compared in a | |||||
case-insensitive way | |||||
*/ | |||||
PropertySet (bool ignoreCaseOfKeyNames = false); | |||||
/** Creates a copy of another PropertySet. */ | |||||
PropertySet (const PropertySet& other); | |||||
/** Copies another PropertySet over this one. */ | |||||
PropertySet& operator= (const PropertySet& other); | |||||
/** Destructor. */ | |||||
virtual ~PropertySet(); | |||||
//============================================================================== | |||||
/** Returns one of the properties as a string. | |||||
If the value isn't found in this set, then this will look for it in a fallback | |||||
property set (if you've specified one with the setFallbackPropertySet() method), | |||||
and if it can't find one there, it'll return the default value passed-in. | |||||
@param keyName the name of the property to retrieve | |||||
@param defaultReturnValue a value to return if the named property doesn't actually exist | |||||
*/ | |||||
String getValue (StringRef keyName, const String& defaultReturnValue = String()) const noexcept; | |||||
/** Returns one of the properties as an integer. | |||||
If the value isn't found in this set, then this will look for it in a fallback | |||||
property set (if you've specified one with the setFallbackPropertySet() method), | |||||
and if it can't find one there, it'll return the default value passed-in. | |||||
@param keyName the name of the property to retrieve | |||||
@param defaultReturnValue a value to return if the named property doesn't actually exist | |||||
*/ | |||||
int getIntValue (StringRef keyName, int defaultReturnValue = 0) const noexcept; | |||||
/** Returns one of the properties as an double. | |||||
If the value isn't found in this set, then this will look for it in a fallback | |||||
property set (if you've specified one with the setFallbackPropertySet() method), | |||||
and if it can't find one there, it'll return the default value passed-in. | |||||
@param keyName the name of the property to retrieve | |||||
@param defaultReturnValue a value to return if the named property doesn't actually exist | |||||
*/ | |||||
double getDoubleValue (StringRef keyName, double defaultReturnValue = 0.0) const noexcept; | |||||
/** Returns one of the properties as an boolean. | |||||
The result will be true if the string found for this key name can be parsed as a non-zero | |||||
integer. | |||||
If the value isn't found in this set, then this will look for it in a fallback | |||||
property set (if you've specified one with the setFallbackPropertySet() method), | |||||
and if it can't find one there, it'll return the default value passed-in. | |||||
@param keyName the name of the property to retrieve | |||||
@param defaultReturnValue a value to return if the named property doesn't actually exist | |||||
*/ | |||||
bool getBoolValue (StringRef keyName, bool defaultReturnValue = false) const noexcept; | |||||
/** Returns one of the properties as an XML element. | |||||
The result will a new XMLElement object that the caller must delete. If may return nullptr | |||||
if the key isn't found, or if the entry contains an string that isn't valid XML. | |||||
If the value isn't found in this set, then this will look for it in a fallback | |||||
property set (if you've specified one with the setFallbackPropertySet() method), | |||||
and if it can't find one there, it'll return the default value passed-in. | |||||
@param keyName the name of the property to retrieve | |||||
*/ | |||||
XmlElement* getXmlValue (StringRef keyName) const; | |||||
//============================================================================== | |||||
/** Sets a named property. | |||||
@param keyName the name of the property to set. (This mustn't be an empty string) | |||||
@param value the new value to set it to | |||||
*/ | |||||
void setValue (const String& keyName, const var& value); | |||||
/** Sets a named property to an XML element. | |||||
@param keyName the name of the property to set. (This mustn't be an empty string) | |||||
@param xml the new element to set it to. If this is a nullptr, the value will | |||||
be set to an empty string | |||||
@see getXmlValue | |||||
*/ | |||||
void setValue (const String& keyName, const XmlElement* xml); | |||||
/** This copies all the values from a source PropertySet to this one. | |||||
This won't remove any existing settings, it just adds any that it finds in the source set. | |||||
*/ | |||||
void addAllPropertiesFrom (const PropertySet& source); | |||||
//============================================================================== | |||||
/** Deletes a property. | |||||
@param keyName the name of the property to delete. (This mustn't be an empty string) | |||||
*/ | |||||
void removeValue (StringRef keyName); | |||||
/** Returns true if the properies include the given key. */ | |||||
bool containsKey (StringRef keyName) const noexcept; | |||||
/** Removes all values. */ | |||||
void clear(); | |||||
//============================================================================== | |||||
/** Returns the keys/value pair array containing all the properties. */ | |||||
StringPairArray& getAllProperties() noexcept { return properties; } | |||||
/** Returns the lock used when reading or writing to this set */ | |||||
const CriticalSection& getLock() const noexcept { return lock; } | |||||
//============================================================================== | |||||
/** Returns an XML element which encapsulates all the items in this property set. | |||||
The string parameter is the tag name that should be used for the node. | |||||
@see restoreFromXml | |||||
*/ | |||||
XmlElement* createXml (const String& nodeName) const; | |||||
/** Reloads a set of properties that were previously stored as XML. | |||||
The node passed in must have been created by the createXml() method. | |||||
@see createXml | |||||
*/ | |||||
void restoreFromXml (const XmlElement& xml); | |||||
//============================================================================== | |||||
/** Sets up a second PopertySet that will be used to look up any values that aren't | |||||
set in this one. | |||||
If you set this up to be a pointer to a second property set, then whenever one | |||||
of the getValue() methods fails to find an entry in this set, it will look up that | |||||
value in the fallback set, and if it finds it, it will return that. | |||||
Make sure that you don't delete the fallback set while it's still being used by | |||||
another set! To remove the fallback set, just call this method with a null pointer. | |||||
@see getFallbackPropertySet | |||||
*/ | |||||
void setFallbackPropertySet (PropertySet* fallbackProperties) noexcept; | |||||
/** Returns the fallback property set. | |||||
@see setFallbackPropertySet | |||||
*/ | |||||
PropertySet* getFallbackPropertySet() const noexcept { return fallbackProperties; } | |||||
protected: | |||||
/** Subclasses can override this to be told when one of the properies has been changed. */ | |||||
virtual void propertyChanged(); | |||||
private: | |||||
StringPairArray properties; | |||||
PropertySet* fallbackProperties; | |||||
CriticalSection lock; | |||||
bool ignoreCaseOfKeys; | |||||
JUCE_LEAK_DETECTOR (PropertySet) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,913 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Holds a list of objects derived from ReferenceCountedObject, or which implement basic | |||||
reference-count handling methods. | |||||
The template parameter specifies the class of the object you want to point to - the easiest | |||||
way to make a class reference-countable is to simply make it inherit from ReferenceCountedObject | |||||
or SingleThreadedReferenceCountedObject, but if you need to, you can roll your own reference-countable | |||||
class by implementing a set of methods called incReferenceCount(), decReferenceCount(), and | |||||
decReferenceCountWithoutDeleting(). See ReferenceCountedObject for examples of how these methods | |||||
should behave. | |||||
A ReferenceCountedArray holds objects derived from ReferenceCountedObject, | |||||
and takes care of incrementing and decrementing their ref counts when they | |||||
are added and removed from the array. | |||||
To make all the array's methods thread-safe, pass in "CriticalSection" as the templated | |||||
TypeOfCriticalSectionToUse parameter, instead of the default DummyCriticalSection. | |||||
@see Array, OwnedArray, StringArray | |||||
*/ | |||||
template <class ObjectClass, class TypeOfCriticalSectionToUse = DummyCriticalSection> | |||||
class ReferenceCountedArray | |||||
{ | |||||
public: | |||||
typedef ReferenceCountedObjectPtr<ObjectClass> ObjectClassPtr; | |||||
//============================================================================== | |||||
/** Creates an empty array. | |||||
@see ReferenceCountedObject, Array, OwnedArray | |||||
*/ | |||||
ReferenceCountedArray() noexcept | |||||
: numUsed (0) | |||||
{ | |||||
} | |||||
/** Creates a copy of another array */ | |||||
ReferenceCountedArray (const ReferenceCountedArray& other) noexcept | |||||
{ | |||||
const ScopedLockType lock (other.getLock()); | |||||
numUsed = other.size(); | |||||
data.setAllocatedSize (numUsed); | |||||
memcpy (data.elements, other.getRawDataPointer(), (size_t) numUsed * sizeof (ObjectClass*)); | |||||
for (int i = numUsed; --i >= 0;) | |||||
if (ObjectClass* o = data.elements[i]) | |||||
o->incReferenceCount(); | |||||
} | |||||
/** Creates a copy of another array */ | |||||
template <class OtherObjectClass, class OtherCriticalSection> | |||||
ReferenceCountedArray (const ReferenceCountedArray<OtherObjectClass, OtherCriticalSection>& other) noexcept | |||||
{ | |||||
const typename ReferenceCountedArray<OtherObjectClass, OtherCriticalSection>::ScopedLockType lock (other.getLock()); | |||||
numUsed = other.size(); | |||||
data.setAllocatedSize (numUsed); | |||||
memcpy (data.elements, other.getRawDataPointer(), numUsed * sizeof (ObjectClass*)); | |||||
for (int i = numUsed; --i >= 0;) | |||||
if (ObjectClass* o = data.elements[i]) | |||||
o->incReferenceCount(); | |||||
} | |||||
/** Copies another array into this one. | |||||
Any existing objects in this array will first be released. | |||||
*/ | |||||
ReferenceCountedArray& operator= (const ReferenceCountedArray& other) noexcept | |||||
{ | |||||
ReferenceCountedArray otherCopy (other); | |||||
swapWith (otherCopy); | |||||
return *this; | |||||
} | |||||
/** Copies another array into this one. | |||||
Any existing objects in this array will first be released. | |||||
*/ | |||||
template <class OtherObjectClass> | |||||
ReferenceCountedArray<ObjectClass, TypeOfCriticalSectionToUse>& operator= (const ReferenceCountedArray<OtherObjectClass, TypeOfCriticalSectionToUse>& other) noexcept | |||||
{ | |||||
ReferenceCountedArray<ObjectClass, TypeOfCriticalSectionToUse> otherCopy (other); | |||||
swapWith (otherCopy); | |||||
return *this; | |||||
} | |||||
/** Destructor. | |||||
Any objects in the array will be released, and may be deleted if not referenced from elsewhere. | |||||
*/ | |||||
~ReferenceCountedArray() | |||||
{ | |||||
releaseAllObjects(); | |||||
} | |||||
//============================================================================== | |||||
/** Removes all objects from the array. | |||||
Any objects in the array that whose reference counts drop to zero will be deleted. | |||||
*/ | |||||
void clear() | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
releaseAllObjects(); | |||||
data.setAllocatedSize (0); | |||||
} | |||||
/** Removes all objects from the array without freeing the array's allocated storage. | |||||
Any objects in the array that whose reference counts drop to zero will be deleted. | |||||
@see clear | |||||
*/ | |||||
void clearQuick() | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
releaseAllObjects(); | |||||
} | |||||
/** Returns the current number of objects in the array. */ | |||||
inline int size() const noexcept | |||||
{ | |||||
return numUsed; | |||||
} | |||||
/** Returns true if the array is empty, false otherwise. */ | |||||
inline bool isEmpty() const noexcept | |||||
{ | |||||
return size() == 0; | |||||
} | |||||
/** Returns a pointer to the object at this index in the array. | |||||
If the index is out-of-range, this will return a null pointer, (and | |||||
it could be null anyway, because it's ok for the array to hold null | |||||
pointers as well as objects). | |||||
@see getUnchecked | |||||
*/ | |||||
inline ObjectClassPtr operator[] (const int index) const noexcept | |||||
{ | |||||
return getObjectPointer (index); | |||||
} | |||||
/** Returns a pointer to the object at this index in the array, without checking | |||||
whether the index is in-range. | |||||
This is a faster and less safe version of operator[] which doesn't check the index passed in, so | |||||
it can be used when you're sure the index is always going to be legal. | |||||
*/ | |||||
inline ObjectClassPtr getUnchecked (const int index) const noexcept | |||||
{ | |||||
return getObjectPointerUnchecked (index); | |||||
} | |||||
/** Returns a raw pointer to the object at this index in the array. | |||||
If the index is out-of-range, this will return a null pointer, (and | |||||
it could be null anyway, because it's ok for the array to hold null | |||||
pointers as well as objects). | |||||
@see getUnchecked | |||||
*/ | |||||
inline ObjectClass* getObjectPointer (const int index) const noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
if (isPositiveAndBelow (index, numUsed)) | |||||
{ | |||||
jassert (data.elements != nullptr); | |||||
return data.elements [index]; | |||||
} | |||||
return ObjectClassPtr(); | |||||
} | |||||
/** Returns a raw pointer to the object at this index in the array, without checking | |||||
whether the index is in-range. | |||||
*/ | |||||
inline ObjectClass* getObjectPointerUnchecked (const int index) const noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
jassert (isPositiveAndBelow (index, numUsed) && data.elements != nullptr); | |||||
return data.elements [index]; | |||||
} | |||||
/** Returns a pointer to the first object in the array. | |||||
This will return a null pointer if the array's empty. | |||||
@see getLast | |||||
*/ | |||||
inline ObjectClassPtr getFirst() const noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
if (numUsed > 0) | |||||
{ | |||||
jassert (data.elements != nullptr); | |||||
return data.elements [0]; | |||||
} | |||||
return ObjectClassPtr(); | |||||
} | |||||
/** Returns a pointer to the last object in the array. | |||||
This will return a null pointer if the array's empty. | |||||
@see getFirst | |||||
*/ | |||||
inline ObjectClassPtr getLast() const noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
if (numUsed > 0) | |||||
{ | |||||
jassert (data.elements != nullptr); | |||||
return data.elements [numUsed - 1]; | |||||
} | |||||
return ObjectClassPtr(); | |||||
} | |||||
/** Returns a pointer to the actual array data. | |||||
This pointer will only be valid until the next time a non-const method | |||||
is called on the array. | |||||
*/ | |||||
inline ObjectClass** getRawDataPointer() const noexcept | |||||
{ | |||||
return data.elements; | |||||
} | |||||
//============================================================================== | |||||
/** Returns a pointer to the first element in the array. | |||||
This method is provided for compatibility with standard C++ iteration mechanisms. | |||||
*/ | |||||
inline ObjectClass** begin() const noexcept | |||||
{ | |||||
return data.elements; | |||||
} | |||||
/** Returns a pointer to the element which follows the last element in the array. | |||||
This method is provided for compatibility with standard C++ iteration mechanisms. | |||||
*/ | |||||
inline ObjectClass** end() const noexcept | |||||
{ | |||||
return data.elements + numUsed; | |||||
} | |||||
//============================================================================== | |||||
/** Finds the index of the first occurrence of an object in the array. | |||||
@param objectToLookFor the object to look for | |||||
@returns the index at which the object was found, or -1 if it's not found | |||||
*/ | |||||
int indexOf (const ObjectClass* const objectToLookFor) const noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
ObjectClass** e = data.elements.get(); | |||||
ObjectClass** const endPointer = e + numUsed; | |||||
while (e != endPointer) | |||||
{ | |||||
if (objectToLookFor == *e) | |||||
return static_cast<int> (e - data.elements.get()); | |||||
++e; | |||||
} | |||||
return -1; | |||||
} | |||||
/** Returns true if the array contains a specified object. | |||||
@param objectToLookFor the object to look for | |||||
@returns true if the object is in the array | |||||
*/ | |||||
bool contains (const ObjectClass* const objectToLookFor) const noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
ObjectClass** e = data.elements.get(); | |||||
ObjectClass** const endPointer = e + numUsed; | |||||
while (e != endPointer) | |||||
{ | |||||
if (objectToLookFor == *e) | |||||
return true; | |||||
++e; | |||||
} | |||||
return false; | |||||
} | |||||
/** Appends a new object to the end of the array. | |||||
This will increase the new object's reference count. | |||||
@param newObject the new object to add to the array | |||||
@see set, insert, addIfNotAlreadyThere, addSorted, addArray | |||||
*/ | |||||
ObjectClass* add (ObjectClass* const newObject) noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
data.ensureAllocatedSize (numUsed + 1); | |||||
jassert (data.elements != nullptr); | |||||
data.elements [numUsed++] = newObject; | |||||
if (newObject != nullptr) | |||||
newObject->incReferenceCount(); | |||||
return newObject; | |||||
} | |||||
/** Inserts a new object into the array at the given index. | |||||
If the index is less than 0 or greater than the size of the array, the | |||||
element will be added to the end of the array. | |||||
Otherwise, it will be inserted into the array, moving all the later elements | |||||
along to make room. | |||||
This will increase the new object's reference count. | |||||
@param indexToInsertAt the index at which the new element should be inserted | |||||
@param newObject the new object to add to the array | |||||
@see add, addSorted, addIfNotAlreadyThere, set | |||||
*/ | |||||
ObjectClass* insert (int indexToInsertAt, | |||||
ObjectClass* const newObject) noexcept | |||||
{ | |||||
if (indexToInsertAt < 0) | |||||
return add (newObject); | |||||
const ScopedLockType lock (getLock()); | |||||
if (indexToInsertAt > numUsed) | |||||
indexToInsertAt = numUsed; | |||||
data.ensureAllocatedSize (numUsed + 1); | |||||
jassert (data.elements != nullptr); | |||||
ObjectClass** const e = data.elements + indexToInsertAt; | |||||
const int numToMove = numUsed - indexToInsertAt; | |||||
if (numToMove > 0) | |||||
memmove (e + 1, e, sizeof (ObjectClass*) * (size_t) numToMove); | |||||
*e = newObject; | |||||
if (newObject != nullptr) | |||||
newObject->incReferenceCount(); | |||||
++numUsed; | |||||
return newObject; | |||||
} | |||||
/** Appends a new object at the end of the array as long as the array doesn't | |||||
already contain it. | |||||
If the array already contains a matching object, nothing will be done. | |||||
@param newObject the new object to add to the array | |||||
@returns true if the object has been added, false otherwise | |||||
*/ | |||||
bool addIfNotAlreadyThere (ObjectClass* const newObject) noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
if (contains (newObject)) | |||||
return false; | |||||
add (newObject); | |||||
return true; | |||||
} | |||||
/** Replaces an object in the array with a different one. | |||||
If the index is less than zero, this method does nothing. | |||||
If the index is beyond the end of the array, the new object is added to the end of the array. | |||||
The object being added has its reference count increased, and if it's replacing | |||||
another object, then that one has its reference count decreased, and may be deleted. | |||||
@param indexToChange the index whose value you want to change | |||||
@param newObject the new value to set for this index. | |||||
@see add, insert, remove | |||||
*/ | |||||
void set (const int indexToChange, | |||||
ObjectClass* const newObject) | |||||
{ | |||||
if (indexToChange >= 0) | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
if (newObject != nullptr) | |||||
newObject->incReferenceCount(); | |||||
if (indexToChange < numUsed) | |||||
{ | |||||
if (ObjectClass* o = data.elements [indexToChange]) | |||||
releaseObject (o); | |||||
data.elements [indexToChange] = newObject; | |||||
} | |||||
else | |||||
{ | |||||
data.ensureAllocatedSize (numUsed + 1); | |||||
jassert (data.elements != nullptr); | |||||
data.elements [numUsed++] = newObject; | |||||
} | |||||
} | |||||
} | |||||
/** Adds elements from another array to the end of this array. | |||||
@param arrayToAddFrom the array from which to copy the elements | |||||
@param startIndex the first element of the other array to start copying from | |||||
@param numElementsToAdd how many elements to add from the other array. If this | |||||
value is negative or greater than the number of available elements, | |||||
all available elements will be copied. | |||||
@see add | |||||
*/ | |||||
void addArray (const ReferenceCountedArray<ObjectClass, TypeOfCriticalSectionToUse>& arrayToAddFrom, | |||||
int startIndex = 0, | |||||
int numElementsToAdd = -1) noexcept | |||||
{ | |||||
const ScopedLockType lock1 (arrayToAddFrom.getLock()); | |||||
{ | |||||
const ScopedLockType lock2 (getLock()); | |||||
if (startIndex < 0) | |||||
{ | |||||
jassertfalse; | |||||
startIndex = 0; | |||||
} | |||||
if (numElementsToAdd < 0 || startIndex + numElementsToAdd > arrayToAddFrom.size()) | |||||
numElementsToAdd = arrayToAddFrom.size() - startIndex; | |||||
if (numElementsToAdd > 0) | |||||
{ | |||||
data.ensureAllocatedSize (numUsed + numElementsToAdd); | |||||
while (--numElementsToAdd >= 0) | |||||
add (arrayToAddFrom.getUnchecked (startIndex++)); | |||||
} | |||||
} | |||||
} | |||||
/** Inserts a new object into the array assuming that the array is sorted. | |||||
This will use a comparator to find the position at which the new object | |||||
should go. If the array isn't sorted, the behaviour of this | |||||
method will be unpredictable. | |||||
@param comparator the comparator object to use to compare the elements - see the | |||||
sort() method for details about this object's form | |||||
@param newObject the new object to insert to the array | |||||
@returns the index at which the new object was added | |||||
@see add, sort | |||||
*/ | |||||
template <class ElementComparator> | |||||
int addSorted (ElementComparator& comparator, ObjectClass* newObject) noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
const int index = findInsertIndexInSortedArray (comparator, data.elements.get(), newObject, 0, numUsed); | |||||
insert (index, newObject); | |||||
return index; | |||||
} | |||||
/** Inserts or replaces an object in the array, assuming it is sorted. | |||||
This is similar to addSorted, but if a matching element already exists, then it will be | |||||
replaced by the new one, rather than the new one being added as well. | |||||
*/ | |||||
template <class ElementComparator> | |||||
void addOrReplaceSorted (ElementComparator& comparator, | |||||
ObjectClass* newObject) noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
const int index = findInsertIndexInSortedArray (comparator, data.elements.get(), newObject, 0, numUsed); | |||||
if (index > 0 && comparator.compareElements (newObject, data.elements [index - 1]) == 0) | |||||
set (index - 1, newObject); // replace an existing object that matches | |||||
else | |||||
insert (index, newObject); // no match, so insert the new one | |||||
} | |||||
/** Finds the index of an object in the array, assuming that the array is sorted. | |||||
This will use a comparator to do a binary-chop to find the index of the given | |||||
element, if it exists. If the array isn't sorted, the behaviour of this | |||||
method will be unpredictable. | |||||
@param comparator the comparator to use to compare the elements - see the sort() | |||||
method for details about the form this object should take | |||||
@param objectToLookFor the object to search for | |||||
@returns the index of the element, or -1 if it's not found | |||||
@see addSorted, sort | |||||
*/ | |||||
template <class ElementComparator> | |||||
int indexOfSorted (ElementComparator& comparator, | |||||
const ObjectClass* const objectToLookFor) const noexcept | |||||
{ | |||||
ignoreUnused (comparator); | |||||
const ScopedLockType lock (getLock()); | |||||
int s = 0, e = numUsed; | |||||
while (s < e) | |||||
{ | |||||
if (comparator.compareElements (objectToLookFor, data.elements [s]) == 0) | |||||
return s; | |||||
const int halfway = (s + e) / 2; | |||||
if (halfway == s) | |||||
break; | |||||
if (comparator.compareElements (objectToLookFor, data.elements [halfway]) >= 0) | |||||
s = halfway; | |||||
else | |||||
e = halfway; | |||||
} | |||||
return -1; | |||||
} | |||||
//============================================================================== | |||||
/** Removes an object from the array. | |||||
This will remove the object at a given index and move back all the | |||||
subsequent objects to close the gap. | |||||
If the index passed in is out-of-range, nothing will happen. | |||||
The object that is removed will have its reference count decreased, | |||||
and may be deleted if not referenced from elsewhere. | |||||
@param indexToRemove the index of the element to remove | |||||
@see removeObject, removeRange | |||||
*/ | |||||
void remove (const int indexToRemove) | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
if (isPositiveAndBelow (indexToRemove, numUsed)) | |||||
{ | |||||
ObjectClass** const e = data.elements + indexToRemove; | |||||
if (ObjectClass* o = *e) | |||||
releaseObject (o); | |||||
--numUsed; | |||||
const int numberToShift = numUsed - indexToRemove; | |||||
if (numberToShift > 0) | |||||
memmove (e, e + 1, sizeof (ObjectClass*) * (size_t) numberToShift); | |||||
if ((numUsed << 1) < data.numAllocated) | |||||
minimiseStorageOverheads(); | |||||
} | |||||
} | |||||
/** Removes and returns an object from the array. | |||||
This will remove the object at a given index and return it, moving back all | |||||
the subsequent objects to close the gap. If the index passed in is out-of-range, | |||||
nothing will happen and a null pointer will be returned. | |||||
@param indexToRemove the index of the element to remove | |||||
@see remove, removeObject, removeRange | |||||
*/ | |||||
ObjectClassPtr removeAndReturn (const int indexToRemove) | |||||
{ | |||||
ObjectClassPtr removedItem; | |||||
const ScopedLockType lock (getLock()); | |||||
if (isPositiveAndBelow (indexToRemove, numUsed)) | |||||
{ | |||||
ObjectClass** const e = data.elements + indexToRemove; | |||||
if (ObjectClass* o = *e) | |||||
{ | |||||
removedItem = o; | |||||
releaseObject (o); | |||||
} | |||||
--numUsed; | |||||
const int numberToShift = numUsed - indexToRemove; | |||||
if (numberToShift > 0) | |||||
memmove (e, e + 1, sizeof (ObjectClass*) * (size_t) numberToShift); | |||||
if ((numUsed << 1) < data.numAllocated) | |||||
minimiseStorageOverheads(); | |||||
} | |||||
return removedItem; | |||||
} | |||||
/** Removes the first occurrence of a specified object from the array. | |||||
If the item isn't found, no action is taken. If it is found, it is | |||||
removed and has its reference count decreased. | |||||
@param objectToRemove the object to try to remove | |||||
@see remove, removeRange | |||||
*/ | |||||
void removeObject (ObjectClass* const objectToRemove) | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
remove (indexOf (objectToRemove)); | |||||
} | |||||
/** Removes a range of objects from the array. | |||||
This will remove a set of objects, starting from the given index, | |||||
and move any subsequent elements down to close the gap. | |||||
If the range extends beyond the bounds of the array, it will | |||||
be safely clipped to the size of the array. | |||||
The objects that are removed will have their reference counts decreased, | |||||
and may be deleted if not referenced from elsewhere. | |||||
@param startIndex the index of the first object to remove | |||||
@param numberToRemove how many objects should be removed | |||||
@see remove, removeObject | |||||
*/ | |||||
void removeRange (const int startIndex, | |||||
const int numberToRemove) | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
const int start = jlimit (0, numUsed, startIndex); | |||||
const int endIndex = jlimit (0, numUsed, startIndex + numberToRemove); | |||||
if (endIndex > start) | |||||
{ | |||||
int i; | |||||
for (i = start; i < endIndex; ++i) | |||||
{ | |||||
if (ObjectClass* o = data.elements[i]) | |||||
{ | |||||
releaseObject (o); | |||||
data.elements[i] = nullptr; // (in case one of the destructors accesses this array and hits a dangling pointer) | |||||
} | |||||
} | |||||
const int rangeSize = endIndex - start; | |||||
ObjectClass** e = data.elements + start; | |||||
i = numUsed - endIndex; | |||||
numUsed -= rangeSize; | |||||
while (--i >= 0) | |||||
{ | |||||
*e = e [rangeSize]; | |||||
++e; | |||||
} | |||||
if ((numUsed << 1) < data.numAllocated) | |||||
minimiseStorageOverheads(); | |||||
} | |||||
} | |||||
/** Removes the last n objects from the array. | |||||
The objects that are removed will have their reference counts decreased, | |||||
and may be deleted if not referenced from elsewhere. | |||||
@param howManyToRemove how many objects to remove from the end of the array | |||||
@see remove, removeObject, removeRange | |||||
*/ | |||||
void removeLast (int howManyToRemove = 1) | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
if (howManyToRemove > numUsed) | |||||
howManyToRemove = numUsed; | |||||
while (--howManyToRemove >= 0) | |||||
remove (numUsed - 1); | |||||
} | |||||
/** Swaps a pair of objects in the array. | |||||
If either of the indexes passed in is out-of-range, nothing will happen, | |||||
otherwise the two objects at these positions will be exchanged. | |||||
*/ | |||||
void swap (const int index1, | |||||
const int index2) noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
if (isPositiveAndBelow (index1, numUsed) | |||||
&& isPositiveAndBelow (index2, numUsed)) | |||||
{ | |||||
std::swap (data.elements [index1], | |||||
data.elements [index2]); | |||||
} | |||||
} | |||||
/** Moves one of the objects to a different position. | |||||
This will move the object to a specified index, shuffling along | |||||
any intervening elements as required. | |||||
So for example, if you have the array { 0, 1, 2, 3, 4, 5 } then calling | |||||
move (2, 4) would result in { 0, 1, 3, 4, 2, 5 }. | |||||
@param currentIndex the index of the object to be moved. If this isn't a | |||||
valid index, then nothing will be done | |||||
@param newIndex the index at which you'd like this object to end up. If this | |||||
is less than zero, it will be moved to the end of the array | |||||
*/ | |||||
void move (const int currentIndex, | |||||
int newIndex) noexcept | |||||
{ | |||||
if (currentIndex != newIndex) | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
if (isPositiveAndBelow (currentIndex, numUsed)) | |||||
{ | |||||
if (! isPositiveAndBelow (newIndex, numUsed)) | |||||
newIndex = numUsed - 1; | |||||
ObjectClass* const value = data.elements [currentIndex]; | |||||
if (newIndex > currentIndex) | |||||
{ | |||||
memmove (data.elements + currentIndex, | |||||
data.elements + currentIndex + 1, | |||||
sizeof (ObjectClass*) * (size_t) (newIndex - currentIndex)); | |||||
} | |||||
else | |||||
{ | |||||
memmove (data.elements + newIndex + 1, | |||||
data.elements + newIndex, | |||||
sizeof (ObjectClass*) * (size_t) (currentIndex - newIndex)); | |||||
} | |||||
data.elements [newIndex] = value; | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
/** This swaps the contents of this array with those of another array. | |||||
If you need to exchange two arrays, this is vastly quicker than using copy-by-value | |||||
because it just swaps their internal pointers. | |||||
*/ | |||||
template <class OtherArrayType> | |||||
void swapWith (OtherArrayType& otherArray) noexcept | |||||
{ | |||||
const ScopedLockType lock1 (getLock()); | |||||
const typename OtherArrayType::ScopedLockType lock2 (otherArray.getLock()); | |||||
data.swapWith (otherArray.data); | |||||
std::swap (numUsed, otherArray.numUsed); | |||||
} | |||||
//============================================================================== | |||||
/** Compares this array to another one. | |||||
@returns true only if the other array contains the same objects in the same order | |||||
*/ | |||||
bool operator== (const ReferenceCountedArray& other) const noexcept | |||||
{ | |||||
const ScopedLockType lock2 (other.getLock()); | |||||
const ScopedLockType lock1 (getLock()); | |||||
if (numUsed != other.numUsed) | |||||
return false; | |||||
for (int i = numUsed; --i >= 0;) | |||||
if (data.elements [i] != other.data.elements [i]) | |||||
return false; | |||||
return true; | |||||
} | |||||
/** Compares this array to another one. | |||||
@see operator== | |||||
*/ | |||||
bool operator!= (const ReferenceCountedArray<ObjectClass, TypeOfCriticalSectionToUse>& other) const noexcept | |||||
{ | |||||
return ! operator== (other); | |||||
} | |||||
//============================================================================== | |||||
/** Sorts the elements in the array. | |||||
This will use a comparator object to sort the elements into order. The object | |||||
passed must have a method of the form: | |||||
@code | |||||
int compareElements (ElementType first, ElementType second); | |||||
@endcode | |||||
..and this method must return: | |||||
- a value of < 0 if the first comes before the second | |||||
- a value of 0 if the two objects are equivalent | |||||
- a value of > 0 if the second comes before the first | |||||
To improve performance, the compareElements() method can be declared as static or const. | |||||
@param comparator the comparator to use for comparing elements. | |||||
@param retainOrderOfEquivalentItems if this is true, then items | |||||
which the comparator says are equivalent will be | |||||
kept in the order in which they currently appear | |||||
in the array. This is slower to perform, but may | |||||
be important in some cases. If it's false, a faster | |||||
algorithm is used, but equivalent elements may be | |||||
rearranged. | |||||
@see sortArray | |||||
*/ | |||||
template <class ElementComparator> | |||||
void sort (ElementComparator& comparator, | |||||
const bool retainOrderOfEquivalentItems = false) const noexcept | |||||
{ | |||||
ignoreUnused (comparator); // if you pass in an object with a static compareElements() method, this | |||||
// avoids getting warning messages about the parameter being unused | |||||
const ScopedLockType lock (getLock()); | |||||
sortArray (comparator, data.elements.get(), 0, size() - 1, retainOrderOfEquivalentItems); | |||||
} | |||||
//============================================================================== | |||||
/** Reduces the amount of storage being used by the array. | |||||
Arrays typically allocate slightly more storage than they need, and after | |||||
removing elements, they may have quite a lot of unused space allocated. | |||||
This method will reduce the amount of allocated storage to a minimum. | |||||
*/ | |||||
void minimiseStorageOverheads() noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
data.shrinkToNoMoreThan (numUsed); | |||||
} | |||||
/** Increases the array's internal storage to hold a minimum number of elements. | |||||
Calling this before adding a large known number of elements means that | |||||
the array won't have to keep dynamically resizing itself as the elements | |||||
are added, and it'll therefore be more efficient. | |||||
*/ | |||||
void ensureStorageAllocated (const int minNumElements) | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
data.ensureAllocatedSize (minNumElements); | |||||
} | |||||
//============================================================================== | |||||
/** Returns the CriticalSection that locks this array. | |||||
To lock, you can call getLock().enter() and getLock().exit(), or preferably use | |||||
an object of ScopedLockType as an RAII lock for it. | |||||
*/ | |||||
inline const TypeOfCriticalSectionToUse& getLock() const noexcept { return data; } | |||||
/** Returns the type of scoped lock to use for locking this array */ | |||||
typedef typename TypeOfCriticalSectionToUse::ScopedLockType ScopedLockType; | |||||
//============================================================================== | |||||
#ifndef DOXYGEN | |||||
// Note that the swapWithArray method has been replaced by a more flexible templated version, | |||||
// and renamed "swapWith" to be more consistent with the names used in other classes. | |||||
JUCE_DEPRECATED_WITH_BODY (void swapWithArray (ReferenceCountedArray& other) noexcept, { swapWith (other); }) | |||||
#endif | |||||
private: | |||||
//============================================================================== | |||||
ArrayAllocationBase <ObjectClass*, TypeOfCriticalSectionToUse> data; | |||||
int numUsed; | |||||
void releaseAllObjects() | |||||
{ | |||||
while (numUsed > 0) | |||||
if (ObjectClass* o = data.elements [--numUsed]) | |||||
releaseObject (o); | |||||
jassert (numUsed == 0); | |||||
} | |||||
static void releaseObject (ObjectClass* o) | |||||
{ | |||||
if (o->decReferenceCountWithoutDeleting()) | |||||
ContainerDeletePolicy<ObjectClass>::destroy (o); | |||||
} | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,92 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Helper class providing an RAII-based mechanism for temporarily setting and | |||||
then re-setting a value. | |||||
E.g. @code | |||||
int x = 1; | |||||
{ | |||||
ScopedValueSetter setter (x, 2); | |||||
// x is now 2 | |||||
} | |||||
// x is now 1 again | |||||
{ | |||||
ScopedValueSetter setter (x, 3, 4); | |||||
// x is now 3 | |||||
} | |||||
// x is now 4 | |||||
@endcode | |||||
*/ | |||||
template <typename ValueType> | |||||
class ScopedValueSetter | |||||
{ | |||||
public: | |||||
/** Creates a ScopedValueSetter that will immediately change the specified value to the | |||||
given new value, and will then reset it to its original value when this object is deleted. | |||||
*/ | |||||
ScopedValueSetter (ValueType& valueToSet, | |||||
ValueType newValue) | |||||
: value (valueToSet), | |||||
originalValue (valueToSet) | |||||
{ | |||||
valueToSet = newValue; | |||||
} | |||||
/** Creates a ScopedValueSetter that will immediately change the specified value to the | |||||
given new value, and will then reset it to be valueWhenDeleted when this object is deleted. | |||||
*/ | |||||
ScopedValueSetter (ValueType& valueToSet, | |||||
ValueType newValue, | |||||
ValueType valueWhenDeleted) | |||||
: value (valueToSet), | |||||
originalValue (valueWhenDeleted) | |||||
{ | |||||
valueToSet = newValue; | |||||
} | |||||
~ScopedValueSetter() | |||||
{ | |||||
value = originalValue; | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
ValueType& value; | |||||
const ValueType originalValue; | |||||
JUCE_DECLARE_NON_COPYABLE (ScopedValueSetter) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,486 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
#if JUCE_MSVC | |||||
#pragma warning (push) | |||||
#pragma warning (disable: 4512) | |||||
#endif | |||||
//============================================================================== | |||||
/** | |||||
Holds a set of unique primitive objects, such as ints or doubles. | |||||
A set can only hold one item with a given value, so if for example it's a | |||||
set of integers, attempting to add the same integer twice will do nothing | |||||
the second time. | |||||
Internally, the list of items is kept sorted (which means that whatever | |||||
kind of primitive type is used must support the ==, <, >, <= and >= operators | |||||
to determine the order), and searching the set for known values is very fast | |||||
because it uses a binary-chop method. | |||||
Note that if you're using a class or struct as the element type, it must be | |||||
capable of being copied or moved with a straightforward memcpy, rather than | |||||
needing construction and destruction code. | |||||
To make all the set's methods thread-safe, pass in "CriticalSection" as the templated | |||||
TypeOfCriticalSectionToUse parameter, instead of the default DummyCriticalSection. | |||||
@see Array, OwnedArray, ReferenceCountedArray, StringArray, CriticalSection | |||||
*/ | |||||
template <class ElementType, class TypeOfCriticalSectionToUse = DummyCriticalSection> | |||||
class SortedSet | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an empty set. */ | |||||
// VS2013 doesn't allow defaulted noexcept constructors. | |||||
SortedSet() noexcept {} | |||||
/** Creates a copy of another set. */ | |||||
SortedSet (const SortedSet&) = default; | |||||
/** Creates a copy of another set. */ | |||||
// VS2013 doesn't allow defaulted noexcept constructors. | |||||
SortedSet (SortedSet&& other) noexcept : data (static_cast<decltype(data)&&> (other.data)) {} | |||||
/** Makes a copy of another set. */ | |||||
SortedSet& operator= (const SortedSet&) = default; | |||||
/** Makes a copy of another set. */ | |||||
// VS2013 doesn't allow defaulted noexcept constructors. | |||||
SortedSet& operator= (SortedSet&& other) noexcept { data = static_cast<decltype(data)&&> (other.data); return *this; } | |||||
/** Destructor. */ | |||||
~SortedSet() noexcept {} | |||||
//============================================================================== | |||||
/** Compares this set to another one. | |||||
Two sets are considered equal if they both contain the same set of elements. | |||||
@param other the other set to compare with | |||||
*/ | |||||
bool operator== (const SortedSet<ElementType>& other) const noexcept | |||||
{ | |||||
return data == other.data; | |||||
} | |||||
/** Compares this set to another one. | |||||
Two sets are considered equal if they both contain the same set of elements. | |||||
@param other the other set to compare with | |||||
*/ | |||||
bool operator!= (const SortedSet<ElementType>& other) const noexcept | |||||
{ | |||||
return ! operator== (other); | |||||
} | |||||
//============================================================================== | |||||
/** Removes all elements from the set. | |||||
This will remove all the elements, and free any storage that the set is | |||||
using. To clear it without freeing the storage, use the clearQuick() | |||||
method instead. | |||||
@see clearQuick | |||||
*/ | |||||
void clear() noexcept | |||||
{ | |||||
data.clear(); | |||||
} | |||||
/** Removes all elements from the set without freeing the array's allocated storage. | |||||
@see clear | |||||
*/ | |||||
void clearQuick() noexcept | |||||
{ | |||||
data.clearQuick(); | |||||
} | |||||
//============================================================================== | |||||
/** Returns the current number of elements in the set. */ | |||||
inline int size() const noexcept | |||||
{ | |||||
return data.size(); | |||||
} | |||||
/** Returns true if the set is empty, false otherwise. */ | |||||
inline bool isEmpty() const noexcept | |||||
{ | |||||
return size() == 0; | |||||
} | |||||
/** Returns one of the elements in the set. | |||||
If the index passed in is beyond the range of valid elements, this | |||||
will return zero. | |||||
If you're certain that the index will always be a valid element, you | |||||
can call getUnchecked() instead, which is faster. | |||||
@param index the index of the element being requested (0 is the first element in the set) | |||||
@see getUnchecked, getFirst, getLast | |||||
*/ | |||||
inline ElementType operator[] (const int index) const noexcept | |||||
{ | |||||
return data [index]; | |||||
} | |||||
/** Returns one of the elements in the set, without checking the index passed in. | |||||
Unlike the operator[] method, this will try to return an element without | |||||
checking that the index is within the bounds of the set, so should only | |||||
be used when you're confident that it will always be a valid index. | |||||
@param index the index of the element being requested (0 is the first element in the set) | |||||
@see operator[], getFirst, getLast | |||||
*/ | |||||
inline ElementType getUnchecked (const int index) const noexcept | |||||
{ | |||||
return data.getUnchecked (index); | |||||
} | |||||
/** Returns a direct reference to one of the elements in the set, without checking the index passed in. | |||||
This is like getUnchecked, but returns a direct reference to the element, so that | |||||
you can alter it directly. Obviously this can be dangerous, so only use it when | |||||
absolutely necessary. | |||||
@param index the index of the element being requested (0 is the first element in the array) | |||||
*/ | |||||
inline ElementType& getReference (const int index) const noexcept | |||||
{ | |||||
return data.getReference (index); | |||||
} | |||||
/** Returns the first element in the set, or 0 if the set is empty. | |||||
@see operator[], getUnchecked, getLast | |||||
*/ | |||||
inline ElementType getFirst() const noexcept | |||||
{ | |||||
return data.getFirst(); | |||||
} | |||||
/** Returns the last element in the set, or 0 if the set is empty. | |||||
@see operator[], getUnchecked, getFirst | |||||
*/ | |||||
inline ElementType getLast() const noexcept | |||||
{ | |||||
return data.getLast(); | |||||
} | |||||
//============================================================================== | |||||
/** Returns a pointer to the first element in the set. | |||||
This method is provided for compatibility with standard C++ iteration mechanisms. | |||||
*/ | |||||
inline ElementType* begin() const noexcept | |||||
{ | |||||
return data.begin(); | |||||
} | |||||
/** Returns a pointer to the element which follows the last element in the set. | |||||
This method is provided for compatibility with standard C++ iteration mechanisms. | |||||
*/ | |||||
inline ElementType* end() const noexcept | |||||
{ | |||||
return data.end(); | |||||
} | |||||
//============================================================================== | |||||
/** Finds the index of the first element which matches the value passed in. | |||||
This will search the set for the given object, and return the index | |||||
of its first occurrence. If the object isn't found, the method will return -1. | |||||
@param elementToLookFor the value or object to look for | |||||
@returns the index of the object, or -1 if it's not found | |||||
*/ | |||||
int indexOf (const ElementType& elementToLookFor) const noexcept | |||||
{ | |||||
const ScopedLockType lock (data.getLock()); | |||||
int s = 0; | |||||
int e = data.size(); | |||||
for (;;) | |||||
{ | |||||
if (s >= e) | |||||
return -1; | |||||
if (elementToLookFor == data.getReference (s)) | |||||
return s; | |||||
auto halfway = (s + e) / 2; | |||||
if (halfway == s) | |||||
return -1; | |||||
if (elementToLookFor < data.getReference (halfway)) | |||||
e = halfway; | |||||
else | |||||
s = halfway; | |||||
} | |||||
} | |||||
/** Returns true if the set contains at least one occurrence of an object. | |||||
@param elementToLookFor the value or object to look for | |||||
@returns true if the item is found | |||||
*/ | |||||
bool contains (const ElementType& elementToLookFor) const noexcept | |||||
{ | |||||
return indexOf (elementToLookFor) >= 0; | |||||
} | |||||
//============================================================================== | |||||
/** Adds a new element to the set, (as long as it's not already in there). | |||||
Note that if a matching element already exists, the new value will be assigned | |||||
to the existing one using operator=, so that if there are any differences between | |||||
the objects which were not recognised by the object's operator==, then the | |||||
set will always contain a copy of the most recently added one. | |||||
@param newElement the new object to add to the set | |||||
@returns true if the value was added, or false if it already existed | |||||
@see set, insert, addIfNotAlreadyThere, addSorted, addSet, addArray | |||||
*/ | |||||
bool add (const ElementType& newElement) noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
int s = 0; | |||||
int e = data.size(); | |||||
while (s < e) | |||||
{ | |||||
auto& elem = data.getReference (s); | |||||
if (newElement == elem) | |||||
{ | |||||
elem = newElement; // force an update in case operator== permits differences. | |||||
return false; | |||||
} | |||||
auto halfway = (s + e) / 2; | |||||
bool isBeforeHalfway = (newElement < data.getReference (halfway)); | |||||
if (halfway == s) | |||||
{ | |||||
if (! isBeforeHalfway) | |||||
++s; | |||||
break; | |||||
} | |||||
if (isBeforeHalfway) | |||||
e = halfway; | |||||
else | |||||
s = halfway; | |||||
} | |||||
data.insert (s, newElement); | |||||
return true; | |||||
} | |||||
/** Adds elements from an array to this set. | |||||
@param elementsToAdd the array of elements to add | |||||
@param numElementsToAdd how many elements are in this other array | |||||
@see add | |||||
*/ | |||||
void addArray (const ElementType* elementsToAdd, | |||||
int numElementsToAdd) noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
while (--numElementsToAdd >= 0) | |||||
add (*elementsToAdd++); | |||||
} | |||||
/** Adds elements from another set to this one. | |||||
@param setToAddFrom the set from which to copy the elements | |||||
@param startIndex the first element of the other set to start copying from | |||||
@param numElementsToAdd how many elements to add from the other set. If this | |||||
value is negative or greater than the number of available elements, | |||||
all available elements will be copied. | |||||
@see add | |||||
*/ | |||||
template <class OtherSetType> | |||||
void addSet (const OtherSetType& setToAddFrom, | |||||
int startIndex = 0, | |||||
int numElementsToAdd = -1) noexcept | |||||
{ | |||||
const typename OtherSetType::ScopedLockType lock1 (setToAddFrom.getLock()); | |||||
const ScopedLockType lock2 (getLock()); | |||||
jassert (this != &setToAddFrom); | |||||
if (this != &setToAddFrom) | |||||
{ | |||||
if (startIndex < 0) | |||||
{ | |||||
jassertfalse; | |||||
startIndex = 0; | |||||
} | |||||
if (numElementsToAdd < 0 || startIndex + numElementsToAdd > setToAddFrom.size()) | |||||
numElementsToAdd = setToAddFrom.size() - startIndex; | |||||
if (numElementsToAdd > 0) | |||||
addArray (&setToAddFrom.data.getReference (startIndex), numElementsToAdd); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
/** Removes an element from the set. | |||||
This will remove the element at a given index. | |||||
If the index passed in is out-of-range, nothing will happen. | |||||
@param indexToRemove the index of the element to remove | |||||
@returns the element that has been removed | |||||
@see removeValue, removeRange | |||||
*/ | |||||
ElementType remove (const int indexToRemove) noexcept | |||||
{ | |||||
return data.removeAndReturn (indexToRemove); | |||||
} | |||||
/** Removes an item from the set. | |||||
This will remove the given element from the set, if it's there. | |||||
@param valueToRemove the object to try to remove | |||||
@see remove, removeRange | |||||
*/ | |||||
void removeValue (const ElementType valueToRemove) noexcept | |||||
{ | |||||
const ScopedLockType lock (getLock()); | |||||
data.remove (indexOf (valueToRemove)); | |||||
} | |||||
/** Removes any elements which are also in another set. | |||||
@param otherSet the other set in which to look for elements to remove | |||||
@see removeValuesNotIn, remove, removeValue, removeRange | |||||
*/ | |||||
template <class OtherSetType> | |||||
void removeValuesIn (const OtherSetType& otherSet) noexcept | |||||
{ | |||||
const typename OtherSetType::ScopedLockType lock1 (otherSet.getLock()); | |||||
const ScopedLockType lock2 (getLock()); | |||||
if (this == &otherSet) | |||||
{ | |||||
clear(); | |||||
} | |||||
else if (! otherSet.isEmpty()) | |||||
{ | |||||
for (int i = data.size(); --i >= 0;) | |||||
if (otherSet.contains (data.getReference (i))) | |||||
remove (i); | |||||
} | |||||
} | |||||
/** Removes any elements which are not found in another set. | |||||
Only elements which occur in this other set will be retained. | |||||
@param otherSet the set in which to look for elements NOT to remove | |||||
@see removeValuesIn, remove, removeValue, removeRange | |||||
*/ | |||||
template <class OtherSetType> | |||||
void removeValuesNotIn (const OtherSetType& otherSet) noexcept | |||||
{ | |||||
const typename OtherSetType::ScopedLockType lock1 (otherSet.getLock()); | |||||
const ScopedLockType lock2 (getLock()); | |||||
if (this != &otherSet) | |||||
{ | |||||
if (otherSet.isEmpty()) | |||||
{ | |||||
clear(); | |||||
} | |||||
else | |||||
{ | |||||
for (int i = data.size(); --i >= 0;) | |||||
if (! otherSet.contains (data.getReference (i))) | |||||
remove (i); | |||||
} | |||||
} | |||||
} | |||||
/** This swaps the contents of this array with those of another array. | |||||
If you need to exchange two arrays, this is vastly quicker than using copy-by-value | |||||
because it just swaps their internal pointers. | |||||
*/ | |||||
template <class OtherSetType> | |||||
void swapWith (OtherSetType& otherSet) noexcept | |||||
{ | |||||
data.swapWith (otherSet.data); | |||||
} | |||||
//============================================================================== | |||||
/** Reduces the amount of storage being used by the set. | |||||
Sets typically allocate slightly more storage than they need, and after | |||||
removing elements, they may have quite a lot of unused space allocated. | |||||
This method will reduce the amount of allocated storage to a minimum. | |||||
*/ | |||||
void minimiseStorageOverheads() noexcept | |||||
{ | |||||
data.minimiseStorageOverheads(); | |||||
} | |||||
/** Increases the set's internal storage to hold a minimum number of elements. | |||||
Calling this before adding a large known number of elements means that | |||||
the set won't have to keep dynamically resizing itself as the elements | |||||
are added, and it'll therefore be more efficient. | |||||
*/ | |||||
void ensureStorageAllocated (const int minNumElements) | |||||
{ | |||||
data.ensureStorageAllocated (minNumElements); | |||||
} | |||||
//============================================================================== | |||||
/** Returns the CriticalSection that locks this array. | |||||
To lock, you can call getLock().enter() and getLock().exit(), or preferably use | |||||
an object of ScopedLockType as an RAII lock for it. | |||||
*/ | |||||
inline const TypeOfCriticalSectionToUse& getLock() const noexcept { return data.getLock(); } | |||||
/** Returns the type of scoped lock to use for locking this array */ | |||||
typedef typename TypeOfCriticalSectionToUse::ScopedLockType ScopedLockType; | |||||
private: | |||||
//============================================================================== | |||||
Array<ElementType, TypeOfCriticalSectionToUse> data; | |||||
}; | |||||
#if JUCE_MSVC | |||||
#pragma warning (pop) | |||||
#endif | |||||
} // namespace juce |
@@ -0,0 +1,289 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Holds a set of primitive values, storing them as a set of ranges. | |||||
This container acts like an array, but can efficiently hold large contiguous | |||||
ranges of values. It's quite a specialised class, mostly useful for things | |||||
like keeping the set of selected rows in a listbox. | |||||
The type used as a template parameter must be an integer type, such as int, short, | |||||
int64, etc. | |||||
*/ | |||||
template <class Type> | |||||
class SparseSet | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a new empty set. */ | |||||
SparseSet() | |||||
{ | |||||
} | |||||
/** Creates a copy of another SparseSet. */ | |||||
SparseSet (const SparseSet<Type>& other) | |||||
: values (other.values) | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
/** Clears the set. */ | |||||
void clear() | |||||
{ | |||||
values.clear(); | |||||
} | |||||
/** Checks whether the set is empty. | |||||
This is much quicker than using (size() == 0). | |||||
*/ | |||||
bool isEmpty() const noexcept | |||||
{ | |||||
return values.size() == 0; | |||||
} | |||||
/** Returns the number of values in the set. | |||||
Because of the way the data is stored, this method can take longer if there | |||||
are a lot of items in the set. Use isEmpty() for a quick test of whether there | |||||
are any items. | |||||
*/ | |||||
Type size() const | |||||
{ | |||||
Type total (0); | |||||
for (int i = 0; i < values.size(); i += 2) | |||||
total += values.getUnchecked (i + 1) - values.getUnchecked (i); | |||||
return total; | |||||
} | |||||
/** Returns one of the values in the set. | |||||
@param index the index of the value to retrieve, in the range 0 to (size() - 1). | |||||
@returns the value at this index, or 0 if it's out-of-range | |||||
*/ | |||||
Type operator[] (Type index) const | |||||
{ | |||||
for (int i = 0; i < values.size(); i += 2) | |||||
{ | |||||
const Type start (values.getUnchecked (i)); | |||||
const Type len (values.getUnchecked (i + 1) - start); | |||||
if (index < len) | |||||
return start + index; | |||||
index -= len; | |||||
} | |||||
return Type(); | |||||
} | |||||
/** Checks whether a particular value is in the set. */ | |||||
bool contains (const Type valueToLookFor) const | |||||
{ | |||||
for (int i = 0; i < values.size(); ++i) | |||||
if (valueToLookFor < values.getUnchecked(i)) | |||||
return (i & 1) != 0; | |||||
return false; | |||||
} | |||||
//============================================================================== | |||||
/** Returns the number of contiguous blocks of values. | |||||
@see getRange | |||||
*/ | |||||
int getNumRanges() const noexcept | |||||
{ | |||||
return values.size() >> 1; | |||||
} | |||||
/** Returns one of the contiguous ranges of values stored. | |||||
@param rangeIndex the index of the range to look up, between 0 | |||||
and (getNumRanges() - 1) | |||||
@see getTotalRange | |||||
*/ | |||||
const Range<Type> getRange (const int rangeIndex) const | |||||
{ | |||||
if (isPositiveAndBelow (rangeIndex, getNumRanges())) | |||||
return Range<Type> (values.getUnchecked (rangeIndex << 1), | |||||
values.getUnchecked ((rangeIndex << 1) + 1)); | |||||
return Range<Type>(); | |||||
} | |||||
/** Returns the range between the lowest and highest values in the set. | |||||
@see getRange | |||||
*/ | |||||
Range<Type> getTotalRange() const | |||||
{ | |||||
if (values.size() > 0) | |||||
{ | |||||
jassert ((values.size() & 1) == 0); | |||||
return Range<Type> (values.getUnchecked (0), | |||||
values.getUnchecked (values.size() - 1)); | |||||
} | |||||
return Range<Type>(); | |||||
} | |||||
//============================================================================== | |||||
/** Adds a range of contiguous values to the set. | |||||
e.g. addRange (Range \<int\> (10, 14)) will add (10, 11, 12, 13) to the set. | |||||
*/ | |||||
void addRange (const Range<Type> range) | |||||
{ | |||||
jassert (range.getLength() >= 0); | |||||
if (range.getLength() > 0) | |||||
{ | |||||
removeRange (range); | |||||
values.addUsingDefaultSort (range.getStart()); | |||||
values.addUsingDefaultSort (range.getEnd()); | |||||
simplify(); | |||||
} | |||||
} | |||||
/** Removes a range of values from the set. | |||||
e.g. removeRange (Range\<int\> (10, 14)) will remove (10, 11, 12, 13) from the set. | |||||
*/ | |||||
void removeRange (const Range<Type> rangeToRemove) | |||||
{ | |||||
jassert (rangeToRemove.getLength() >= 0); | |||||
if (rangeToRemove.getLength() > 0 | |||||
&& values.size() > 0 | |||||
&& rangeToRemove.getStart() < values.getUnchecked (values.size() - 1) | |||||
&& values.getUnchecked(0) < rangeToRemove.getEnd()) | |||||
{ | |||||
const bool onAtStart = contains (rangeToRemove.getStart() - 1); | |||||
const Type lastValue (jmin (rangeToRemove.getEnd(), values.getLast())); | |||||
const bool onAtEnd = contains (lastValue); | |||||
for (int i = values.size(); --i >= 0;) | |||||
{ | |||||
if (values.getUnchecked(i) <= lastValue) | |||||
{ | |||||
while (values.getUnchecked(i) >= rangeToRemove.getStart()) | |||||
{ | |||||
values.remove (i); | |||||
if (--i < 0) | |||||
break; | |||||
} | |||||
break; | |||||
} | |||||
} | |||||
if (onAtStart) values.addUsingDefaultSort (rangeToRemove.getStart()); | |||||
if (onAtEnd) values.addUsingDefaultSort (lastValue); | |||||
simplify(); | |||||
} | |||||
} | |||||
/** Does an XOR of the values in a given range. */ | |||||
void invertRange (const Range<Type> range) | |||||
{ | |||||
SparseSet newItems; | |||||
newItems.addRange (range); | |||||
for (int i = getNumRanges(); --i >= 0;) | |||||
newItems.removeRange (getRange (i)); | |||||
removeRange (range); | |||||
for (int i = newItems.getNumRanges(); --i >= 0;) | |||||
addRange (newItems.getRange(i)); | |||||
} | |||||
/** Checks whether any part of a given range overlaps any part of this set. */ | |||||
bool overlapsRange (const Range<Type> range) | |||||
{ | |||||
if (range.getLength() > 0) | |||||
{ | |||||
for (int i = getNumRanges(); --i >= 0;) | |||||
{ | |||||
if (values.getUnchecked ((i << 1) + 1) <= range.getStart()) | |||||
return false; | |||||
if (values.getUnchecked (i << 1) < range.getEnd()) | |||||
return true; | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
/** Checks whether the whole of a given range is contained within this one. */ | |||||
bool containsRange (const Range<Type> range) | |||||
{ | |||||
if (range.getLength() > 0) | |||||
{ | |||||
for (int i = getNumRanges(); --i >= 0;) | |||||
{ | |||||
if (values.getUnchecked ((i << 1) + 1) <= range.getStart()) | |||||
return false; | |||||
if (values.getUnchecked (i << 1) <= range.getStart() | |||||
&& range.getEnd() <= values.getUnchecked ((i << 1) + 1)) | |||||
return true; | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
//============================================================================== | |||||
bool operator== (const SparseSet<Type>& other) noexcept | |||||
{ | |||||
return values == other.values; | |||||
} | |||||
bool operator!= (const SparseSet<Type>& other) noexcept | |||||
{ | |||||
return values != other.values; | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
// alternating start/end values of ranges of values that are present. | |||||
Array<Type, DummyCriticalSection> values; | |||||
void simplify() | |||||
{ | |||||
jassert ((values.size() & 1) == 0); | |||||
for (int i = values.size(); --i > 0;) | |||||
if (values.getUnchecked(i) == values.getUnchecked (i - 1)) | |||||
values.removeRange (--i, 2); | |||||
} | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,803 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
enum VariantStreamMarkers | |||||
{ | |||||
varMarker_Int = 1, | |||||
varMarker_BoolTrue = 2, | |||||
varMarker_BoolFalse = 3, | |||||
varMarker_Double = 4, | |||||
varMarker_String = 5, | |||||
varMarker_Int64 = 6, | |||||
varMarker_Array = 7, | |||||
varMarker_Binary = 8, | |||||
varMarker_Undefined = 9 | |||||
}; | |||||
//============================================================================== | |||||
class var::VariantType | |||||
{ | |||||
public: | |||||
VariantType() noexcept {} | |||||
virtual ~VariantType() noexcept {} | |||||
virtual int toInt (const ValueUnion&) const noexcept { return 0; } | |||||
virtual int64 toInt64 (const ValueUnion&) const noexcept { return 0; } | |||||
virtual double toDouble (const ValueUnion&) const noexcept { return 0; } | |||||
virtual String toString (const ValueUnion&) const { return {}; } | |||||
virtual bool toBool (const ValueUnion&) const noexcept { return false; } | |||||
virtual ReferenceCountedObject* toObject (const ValueUnion&) const noexcept { return nullptr; } | |||||
virtual Array<var>* toArray (const ValueUnion&) const noexcept { return nullptr; } | |||||
virtual MemoryBlock* toBinary (const ValueUnion&) const noexcept { return nullptr; } | |||||
virtual var clone (const var& original) const { return original; } | |||||
virtual bool isVoid() const noexcept { return false; } | |||||
virtual bool isUndefined() const noexcept { return false; } | |||||
virtual bool isInt() const noexcept { return false; } | |||||
virtual bool isInt64() const noexcept { return false; } | |||||
virtual bool isBool() const noexcept { return false; } | |||||
virtual bool isDouble() const noexcept { return false; } | |||||
virtual bool isString() const noexcept { return false; } | |||||
virtual bool isObject() const noexcept { return false; } | |||||
virtual bool isArray() const noexcept { return false; } | |||||
virtual bool isBinary() const noexcept { return false; } | |||||
virtual bool isMethod() const noexcept { return false; } | |||||
virtual void cleanUp (ValueUnion&) const noexcept {} | |||||
virtual void createCopy (ValueUnion& dest, const ValueUnion& source) const { dest = source; } | |||||
virtual bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept = 0; | |||||
virtual void writeToStream (const ValueUnion& data, OutputStream& output) const = 0; | |||||
}; | |||||
//============================================================================== | |||||
class var::VariantType_Void : public var::VariantType | |||||
{ | |||||
public: | |||||
VariantType_Void() noexcept {} | |||||
static const VariantType_Void instance; | |||||
bool isVoid() const noexcept override { return true; } | |||||
bool equals (const ValueUnion&, const ValueUnion&, const VariantType& otherType) const noexcept override { return otherType.isVoid() || otherType.isUndefined(); } | |||||
void writeToStream (const ValueUnion&, OutputStream& output) const override { output.writeCompressedInt (0); } | |||||
}; | |||||
//============================================================================== | |||||
class var::VariantType_Undefined : public var::VariantType | |||||
{ | |||||
public: | |||||
VariantType_Undefined() noexcept {} | |||||
static const VariantType_Undefined instance; | |||||
bool isUndefined() const noexcept override { return true; } | |||||
String toString (const ValueUnion&) const override { return "undefined"; } | |||||
bool equals (const ValueUnion&, const ValueUnion&, const VariantType& otherType) const noexcept override { return otherType.isVoid() || otherType.isUndefined(); } | |||||
void writeToStream (const ValueUnion&, OutputStream& output) const override | |||||
{ | |||||
output.writeCompressedInt (1); | |||||
output.writeByte (varMarker_Undefined); | |||||
} | |||||
}; | |||||
//============================================================================== | |||||
class var::VariantType_Int : public var::VariantType | |||||
{ | |||||
public: | |||||
VariantType_Int() noexcept {} | |||||
static const VariantType_Int instance; | |||||
int toInt (const ValueUnion& data) const noexcept override { return data.intValue; } | |||||
int64 toInt64 (const ValueUnion& data) const noexcept override { return (int64) data.intValue; } | |||||
double toDouble (const ValueUnion& data) const noexcept override { return (double) data.intValue; } | |||||
String toString (const ValueUnion& data) const override { return String (data.intValue); } | |||||
bool toBool (const ValueUnion& data) const noexcept override { return data.intValue != 0; } | |||||
bool isInt() const noexcept override { return true; } | |||||
bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override | |||||
{ | |||||
if (otherType.isDouble() || otherType.isInt64() || otherType.isString()) | |||||
return otherType.equals (otherData, data, *this); | |||||
return otherType.toInt (otherData) == data.intValue; | |||||
} | |||||
void writeToStream (const ValueUnion& data, OutputStream& output) const override | |||||
{ | |||||
output.writeCompressedInt (5); | |||||
output.writeByte (varMarker_Int); | |||||
output.writeInt (data.intValue); | |||||
} | |||||
}; | |||||
//============================================================================== | |||||
class var::VariantType_Int64 : public var::VariantType | |||||
{ | |||||
public: | |||||
VariantType_Int64() noexcept {} | |||||
static const VariantType_Int64 instance; | |||||
int toInt (const ValueUnion& data) const noexcept override { return (int) data.int64Value; } | |||||
int64 toInt64 (const ValueUnion& data) const noexcept override { return data.int64Value; } | |||||
double toDouble (const ValueUnion& data) const noexcept override { return (double) data.int64Value; } | |||||
String toString (const ValueUnion& data) const override { return String (data.int64Value); } | |||||
bool toBool (const ValueUnion& data) const noexcept override { return data.int64Value != 0; } | |||||
bool isInt64() const noexcept override { return true; } | |||||
bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override | |||||
{ | |||||
if (otherType.isDouble() || otherType.isString()) | |||||
return otherType.equals (otherData, data, *this); | |||||
return otherType.toInt64 (otherData) == data.int64Value; | |||||
} | |||||
void writeToStream (const ValueUnion& data, OutputStream& output) const override | |||||
{ | |||||
output.writeCompressedInt (9); | |||||
output.writeByte (varMarker_Int64); | |||||
output.writeInt64 (data.int64Value); | |||||
} | |||||
}; | |||||
//============================================================================== | |||||
class var::VariantType_Double : public var::VariantType | |||||
{ | |||||
public: | |||||
VariantType_Double() noexcept {} | |||||
static const VariantType_Double instance; | |||||
int toInt (const ValueUnion& data) const noexcept override { return (int) data.doubleValue; } | |||||
int64 toInt64 (const ValueUnion& data) const noexcept override { return (int64) data.doubleValue; } | |||||
double toDouble (const ValueUnion& data) const noexcept override { return data.doubleValue; } | |||||
String toString (const ValueUnion& data) const override { return String (data.doubleValue, 20); } | |||||
bool toBool (const ValueUnion& data) const noexcept override { return data.doubleValue != 0.0; } | |||||
bool isDouble() const noexcept override { return true; } | |||||
bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override | |||||
{ | |||||
return std::abs (otherType.toDouble (otherData) - data.doubleValue) < std::numeric_limits<double>::epsilon(); | |||||
} | |||||
void writeToStream (const ValueUnion& data, OutputStream& output) const override | |||||
{ | |||||
output.writeCompressedInt (9); | |||||
output.writeByte (varMarker_Double); | |||||
output.writeDouble (data.doubleValue); | |||||
} | |||||
}; | |||||
//============================================================================== | |||||
class var::VariantType_Bool : public var::VariantType | |||||
{ | |||||
public: | |||||
VariantType_Bool() noexcept {} | |||||
static const VariantType_Bool instance; | |||||
int toInt (const ValueUnion& data) const noexcept override { return data.boolValue ? 1 : 0; } | |||||
int64 toInt64 (const ValueUnion& data) const noexcept override { return data.boolValue ? 1 : 0; } | |||||
double toDouble (const ValueUnion& data) const noexcept override { return data.boolValue ? 1.0 : 0.0; } | |||||
String toString (const ValueUnion& data) const override { return String::charToString (data.boolValue ? (juce_wchar) '1' : (juce_wchar) '0'); } | |||||
bool toBool (const ValueUnion& data) const noexcept override { return data.boolValue; } | |||||
bool isBool() const noexcept override { return true; } | |||||
bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override | |||||
{ | |||||
return otherType.toBool (otherData) == data.boolValue; | |||||
} | |||||
void writeToStream (const ValueUnion& data, OutputStream& output) const override | |||||
{ | |||||
output.writeCompressedInt (1); | |||||
output.writeByte (data.boolValue ? (char) varMarker_BoolTrue : (char) varMarker_BoolFalse); | |||||
} | |||||
}; | |||||
//============================================================================== | |||||
class var::VariantType_String : public var::VariantType | |||||
{ | |||||
public: | |||||
VariantType_String() noexcept {} | |||||
static const VariantType_String instance; | |||||
void cleanUp (ValueUnion& data) const noexcept override { getString (data)-> ~String(); } | |||||
void createCopy (ValueUnion& dest, const ValueUnion& source) const override { new (dest.stringValue) String (*getString (source)); } | |||||
bool isString() const noexcept override { return true; } | |||||
int toInt (const ValueUnion& data) const noexcept override { return getString (data)->getIntValue(); } | |||||
int64 toInt64 (const ValueUnion& data) const noexcept override { return getString (data)->getLargeIntValue(); } | |||||
double toDouble (const ValueUnion& data) const noexcept override { return getString (data)->getDoubleValue(); } | |||||
String toString (const ValueUnion& data) const override { return *getString (data); } | |||||
bool toBool (const ValueUnion& data) const noexcept override { return getString (data)->getIntValue() != 0 | |||||
|| getString (data)->trim().equalsIgnoreCase ("true") | |||||
|| getString (data)->trim().equalsIgnoreCase ("yes"); } | |||||
bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override | |||||
{ | |||||
return otherType.toString (otherData) == *getString (data); | |||||
} | |||||
void writeToStream (const ValueUnion& data, OutputStream& output) const override | |||||
{ | |||||
auto* s = getString (data); | |||||
const size_t len = s->getNumBytesAsUTF8() + 1; | |||||
HeapBlock<char> temp (len); | |||||
s->copyToUTF8 (temp, len); | |||||
output.writeCompressedInt ((int) (len + 1)); | |||||
output.writeByte (varMarker_String); | |||||
output.write (temp, len); | |||||
} | |||||
private: | |||||
static inline const String* getString (const ValueUnion& data) noexcept { return reinterpret_cast<const String*> (data.stringValue); } | |||||
static inline String* getString (ValueUnion& data) noexcept { return reinterpret_cast<String*> (data.stringValue); } | |||||
}; | |||||
//============================================================================== | |||||
class var::VariantType_Object : public var::VariantType | |||||
{ | |||||
public: | |||||
VariantType_Object() noexcept {} | |||||
static const VariantType_Object instance; | |||||
void cleanUp (ValueUnion& data) const noexcept override { if (data.objectValue != nullptr) data.objectValue->decReferenceCount(); } | |||||
void createCopy (ValueUnion& dest, const ValueUnion& source) const override | |||||
{ | |||||
dest.objectValue = source.objectValue; | |||||
if (dest.objectValue != nullptr) | |||||
dest.objectValue->incReferenceCount(); | |||||
} | |||||
String toString (const ValueUnion& data) const override { return "Object 0x" + String::toHexString ((int) (pointer_sized_int) data.objectValue); } | |||||
bool toBool (const ValueUnion& data) const noexcept override { return data.objectValue != nullptr; } | |||||
ReferenceCountedObject* toObject (const ValueUnion& data) const noexcept override { return data.objectValue; } | |||||
bool isObject() const noexcept override { return true; } | |||||
bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override | |||||
{ | |||||
return otherType.toObject (otherData) == data.objectValue; | |||||
} | |||||
var clone (const var& original) const override | |||||
{ | |||||
if (auto* d = original.getDynamicObject()) | |||||
return d->clone().get(); | |||||
jassertfalse; // can only clone DynamicObjects! | |||||
return {}; | |||||
} | |||||
void writeToStream (const ValueUnion&, OutputStream& output) const override | |||||
{ | |||||
jassertfalse; // Can't write an object to a stream! | |||||
output.writeCompressedInt (0); | |||||
} | |||||
}; | |||||
//============================================================================== | |||||
class var::VariantType_Array : public var::VariantType_Object | |||||
{ | |||||
public: | |||||
VariantType_Array() noexcept {} | |||||
static const VariantType_Array instance; | |||||
String toString (const ValueUnion&) const override { return "[Array]"; } | |||||
ReferenceCountedObject* toObject (const ValueUnion&) const noexcept override { return nullptr; } | |||||
bool isArray() const noexcept override { return true; } | |||||
Array<var>* toArray (const ValueUnion& data) const noexcept override | |||||
{ | |||||
if (auto* a = dynamic_cast<RefCountedArray*> (data.objectValue)) | |||||
return &(a->array); | |||||
return nullptr; | |||||
} | |||||
bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override | |||||
{ | |||||
auto* thisArray = toArray (data); | |||||
auto* otherArray = otherType.toArray (otherData); | |||||
return thisArray == otherArray || (thisArray != nullptr && otherArray != nullptr && *otherArray == *thisArray); | |||||
} | |||||
var clone (const var& original) const override | |||||
{ | |||||
Array<var> arrayCopy; | |||||
if (auto* array = toArray (original.value)) | |||||
{ | |||||
arrayCopy.ensureStorageAllocated (array->size()); | |||||
for (auto& i : *array) | |||||
arrayCopy.add (i.clone()); | |||||
} | |||||
return var (arrayCopy); | |||||
} | |||||
void writeToStream (const ValueUnion& data, OutputStream& output) const override | |||||
{ | |||||
if (auto* array = toArray (data)) | |||||
{ | |||||
MemoryOutputStream buffer (512); | |||||
buffer.writeCompressedInt (array->size()); | |||||
for (auto& i : *array) | |||||
i.writeToStream (buffer); | |||||
output.writeCompressedInt (1 + (int) buffer.getDataSize()); | |||||
output.writeByte (varMarker_Array); | |||||
output << buffer; | |||||
} | |||||
} | |||||
struct RefCountedArray : public ReferenceCountedObject | |||||
{ | |||||
RefCountedArray (const Array<var>& a) : array (a) { incReferenceCount(); } | |||||
RefCountedArray (Array<var>&& a) : array (static_cast<Array<var>&&> (a)) { incReferenceCount(); } | |||||
Array<var> array; | |||||
}; | |||||
}; | |||||
//============================================================================== | |||||
class var::VariantType_Binary : public var::VariantType | |||||
{ | |||||
public: | |||||
VariantType_Binary() noexcept {} | |||||
static const VariantType_Binary instance; | |||||
void cleanUp (ValueUnion& data) const noexcept override { delete data.binaryValue; } | |||||
void createCopy (ValueUnion& dest, const ValueUnion& source) const override { dest.binaryValue = new MemoryBlock (*source.binaryValue); } | |||||
String toString (const ValueUnion& data) const override { return data.binaryValue->toBase64Encoding(); } | |||||
bool isBinary() const noexcept override { return true; } | |||||
MemoryBlock* toBinary (const ValueUnion& data) const noexcept override { return data.binaryValue; } | |||||
bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override | |||||
{ | |||||
const MemoryBlock* const otherBlock = otherType.toBinary (otherData); | |||||
return otherBlock != nullptr && *otherBlock == *data.binaryValue; | |||||
} | |||||
void writeToStream (const ValueUnion& data, OutputStream& output) const override | |||||
{ | |||||
output.writeCompressedInt (1 + (int) data.binaryValue->getSize()); | |||||
output.writeByte (varMarker_Binary); | |||||
output << *data.binaryValue; | |||||
} | |||||
}; | |||||
//============================================================================== | |||||
class var::VariantType_Method : public var::VariantType | |||||
{ | |||||
public: | |||||
VariantType_Method() noexcept {} | |||||
static const VariantType_Method instance; | |||||
void cleanUp (ValueUnion& data) const noexcept override { if (data.methodValue != nullptr ) delete data.methodValue; } | |||||
void createCopy (ValueUnion& dest, const ValueUnion& source) const override { dest.methodValue = new NativeFunction (*source.methodValue); } | |||||
String toString (const ValueUnion&) const override { return "Method"; } | |||||
bool toBool (const ValueUnion& data) const noexcept override { return data.methodValue != nullptr; } | |||||
bool isMethod() const noexcept override { return true; } | |||||
bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override | |||||
{ | |||||
return otherType.isMethod() && otherData.methodValue == data.methodValue; | |||||
} | |||||
void writeToStream (const ValueUnion&, OutputStream& output) const override | |||||
{ | |||||
jassertfalse; // Can't write a method to a stream! | |||||
output.writeCompressedInt (0); | |||||
} | |||||
}; | |||||
//============================================================================== | |||||
const var::VariantType_Void var::VariantType_Void::instance; | |||||
const var::VariantType_Undefined var::VariantType_Undefined::instance; | |||||
const var::VariantType_Int var::VariantType_Int::instance; | |||||
const var::VariantType_Int64 var::VariantType_Int64::instance; | |||||
const var::VariantType_Bool var::VariantType_Bool::instance; | |||||
const var::VariantType_Double var::VariantType_Double::instance; | |||||
const var::VariantType_String var::VariantType_String::instance; | |||||
const var::VariantType_Object var::VariantType_Object::instance; | |||||
const var::VariantType_Array var::VariantType_Array::instance; | |||||
const var::VariantType_Binary var::VariantType_Binary::instance; | |||||
const var::VariantType_Method var::VariantType_Method::instance; | |||||
//============================================================================== | |||||
var::var() noexcept : type (&VariantType_Void::instance) {} | |||||
var::var (const VariantType& t) noexcept : type (&t) {} | |||||
var::~var() noexcept { type->cleanUp (value); } | |||||
#if JUCE_ALLOW_STATIC_NULL_VARIABLES | |||||
const var var::null; | |||||
#endif | |||||
//============================================================================== | |||||
var::var (const var& valueToCopy) : type (valueToCopy.type) | |||||
{ | |||||
type->createCopy (value, valueToCopy.value); | |||||
} | |||||
var::var (const int v) noexcept : type (&VariantType_Int::instance) { value.intValue = v; } | |||||
var::var (const int64 v) noexcept : type (&VariantType_Int64::instance) { value.int64Value = v; } | |||||
var::var (const bool v) noexcept : type (&VariantType_Bool::instance) { value.boolValue = v; } | |||||
var::var (const double v) noexcept : type (&VariantType_Double::instance) { value.doubleValue = v; } | |||||
var::var (NativeFunction m) noexcept : type (&VariantType_Method::instance) { value.methodValue = new NativeFunction (m); } | |||||
var::var (const Array<var>& v) : type (&VariantType_Array::instance) { value.objectValue = new VariantType_Array::RefCountedArray(v); } | |||||
var::var (const String& v) : type (&VariantType_String::instance) { new (value.stringValue) String (v); } | |||||
var::var (const char* const v) : type (&VariantType_String::instance) { new (value.stringValue) String (v); } | |||||
var::var (const wchar_t* const v) : type (&VariantType_String::instance) { new (value.stringValue) String (v); } | |||||
var::var (const void* v, size_t sz) : type (&VariantType_Binary::instance) { value.binaryValue = new MemoryBlock (v, sz); } | |||||
var::var (const MemoryBlock& v) : type (&VariantType_Binary::instance) { value.binaryValue = new MemoryBlock (v); } | |||||
var::var (const StringArray& v) : type (&VariantType_Array::instance) | |||||
{ | |||||
Array<var> strings; | |||||
strings.ensureStorageAllocated (v.size()); | |||||
for (auto& i : v) | |||||
strings.add (var (i)); | |||||
value.objectValue = new VariantType_Array::RefCountedArray (strings); | |||||
} | |||||
var::var (ReferenceCountedObject* const object) : type (&VariantType_Object::instance) | |||||
{ | |||||
value.objectValue = object; | |||||
if (object != nullptr) | |||||
object->incReferenceCount(); | |||||
} | |||||
var var::undefined() noexcept { return var (VariantType_Undefined::instance); } | |||||
//============================================================================== | |||||
bool var::isVoid() const noexcept { return type->isVoid(); } | |||||
bool var::isUndefined() const noexcept { return type->isUndefined(); } | |||||
bool var::isInt() const noexcept { return type->isInt(); } | |||||
bool var::isInt64() const noexcept { return type->isInt64(); } | |||||
bool var::isBool() const noexcept { return type->isBool(); } | |||||
bool var::isDouble() const noexcept { return type->isDouble(); } | |||||
bool var::isString() const noexcept { return type->isString(); } | |||||
bool var::isObject() const noexcept { return type->isObject(); } | |||||
bool var::isArray() const noexcept { return type->isArray(); } | |||||
bool var::isBinaryData() const noexcept { return type->isBinary(); } | |||||
bool var::isMethod() const noexcept { return type->isMethod(); } | |||||
var::operator int() const noexcept { return type->toInt (value); } | |||||
var::operator int64() const noexcept { return type->toInt64 (value); } | |||||
var::operator bool() const noexcept { return type->toBool (value); } | |||||
var::operator float() const noexcept { return (float) type->toDouble (value); } | |||||
var::operator double() const noexcept { return type->toDouble (value); } | |||||
String var::toString() const { return type->toString (value); } | |||||
var::operator String() const { return type->toString (value); } | |||||
ReferenceCountedObject* var::getObject() const noexcept { return type->toObject (value); } | |||||
Array<var>* var::getArray() const noexcept { return type->toArray (value); } | |||||
MemoryBlock* var::getBinaryData() const noexcept { return type->toBinary (value); } | |||||
DynamicObject* var::getDynamicObject() const noexcept { return dynamic_cast<DynamicObject*> (getObject()); } | |||||
//============================================================================== | |||||
void var::swapWith (var& other) noexcept | |||||
{ | |||||
std::swap (type, other.type); | |||||
std::swap (value, other.value); | |||||
} | |||||
var& var::operator= (const var& v) { type->cleanUp (value); type = v.type; type->createCopy (value, v.value); return *this; } | |||||
var& var::operator= (const int v) { type->cleanUp (value); type = &VariantType_Int::instance; value.intValue = v; return *this; } | |||||
var& var::operator= (const int64 v) { type->cleanUp (value); type = &VariantType_Int64::instance; value.int64Value = v; return *this; } | |||||
var& var::operator= (const bool v) { type->cleanUp (value); type = &VariantType_Bool::instance; value.boolValue = v; return *this; } | |||||
var& var::operator= (const double v) { type->cleanUp (value); type = &VariantType_Double::instance; value.doubleValue = v; return *this; } | |||||
var& var::operator= (const char* const v) { type->cleanUp (value); type = &VariantType_String::instance; new (value.stringValue) String (v); return *this; } | |||||
var& var::operator= (const wchar_t* const v) { type->cleanUp (value); type = &VariantType_String::instance; new (value.stringValue) String (v); return *this; } | |||||
var& var::operator= (const String& v) { type->cleanUp (value); type = &VariantType_String::instance; new (value.stringValue) String (v); return *this; } | |||||
var& var::operator= (const MemoryBlock& v) { type->cleanUp (value); type = &VariantType_Binary::instance; value.binaryValue = new MemoryBlock (v); return *this; } | |||||
var& var::operator= (const Array<var>& v) { var v2 (v); swapWith (v2); return *this; } | |||||
var& var::operator= (ReferenceCountedObject* v) { var v2 (v); swapWith (v2); return *this; } | |||||
var& var::operator= (NativeFunction v) { var v2 (v); swapWith (v2); return *this; } | |||||
var::var (var&& other) noexcept | |||||
: type (other.type), | |||||
value (other.value) | |||||
{ | |||||
other.type = &VariantType_Void::instance; | |||||
} | |||||
var& var::operator= (var&& other) noexcept | |||||
{ | |||||
swapWith (other); | |||||
return *this; | |||||
} | |||||
var::var (String&& v) : type (&VariantType_String::instance) | |||||
{ | |||||
new (value.stringValue) String (static_cast<String&&> (v)); | |||||
} | |||||
var::var (MemoryBlock&& v) : type (&VariantType_Binary::instance) | |||||
{ | |||||
value.binaryValue = new MemoryBlock (static_cast<MemoryBlock&&> (v)); | |||||
} | |||||
var::var (Array<var>&& v) : type (&VariantType_Array::instance) | |||||
{ | |||||
value.objectValue = new VariantType_Array::RefCountedArray (static_cast<Array<var>&&> (v)); | |||||
} | |||||
var& var::operator= (String&& v) | |||||
{ | |||||
type->cleanUp (value); | |||||
type = &VariantType_String::instance; | |||||
new (value.stringValue) String (static_cast<String&&> (v)); | |||||
return *this; | |||||
} | |||||
//============================================================================== | |||||
bool var::equals (const var& other) const noexcept | |||||
{ | |||||
return type->equals (value, other.value, *other.type); | |||||
} | |||||
bool var::equalsWithSameType (const var& other) const noexcept | |||||
{ | |||||
return type == other.type && equals (other); | |||||
} | |||||
bool var::hasSameTypeAs (const var& other) const noexcept | |||||
{ | |||||
return type == other.type; | |||||
} | |||||
bool operator== (const var& v1, const var& v2) noexcept { return v1.equals (v2); } | |||||
bool operator!= (const var& v1, const var& v2) noexcept { return ! v1.equals (v2); } | |||||
bool operator== (const var& v1, const String& v2) { return v1.toString() == v2; } | |||||
bool operator!= (const var& v1, const String& v2) { return v1.toString() != v2; } | |||||
bool operator== (const var& v1, const char* const v2) { return v1.toString() == v2; } | |||||
bool operator!= (const var& v1, const char* const v2) { return v1.toString() != v2; } | |||||
//============================================================================== | |||||
var var::clone() const noexcept | |||||
{ | |||||
return type->clone (*this); | |||||
} | |||||
//============================================================================== | |||||
const var& var::operator[] (const Identifier& propertyName) const | |||||
{ | |||||
if (auto* o = getDynamicObject()) | |||||
return o->getProperty (propertyName); | |||||
return getNullVarRef(); | |||||
} | |||||
const var& var::operator[] (const char* const propertyName) const | |||||
{ | |||||
return operator[] (Identifier (propertyName)); | |||||
} | |||||
var var::getProperty (const Identifier& propertyName, const var& defaultReturnValue) const | |||||
{ | |||||
if (auto* o = getDynamicObject()) | |||||
return o->getProperties().getWithDefault (propertyName, defaultReturnValue); | |||||
return defaultReturnValue; | |||||
} | |||||
bool var::hasProperty (const Identifier& propertyName) const noexcept | |||||
{ | |||||
if (auto* o = getDynamicObject()) | |||||
return o->hasProperty (propertyName); | |||||
return false; | |||||
} | |||||
var::NativeFunction var::getNativeFunction() const | |||||
{ | |||||
return isMethod() && (value.methodValue != nullptr) ? *value.methodValue : nullptr; | |||||
} | |||||
var var::invoke (const Identifier& method, const var* arguments, int numArguments) const | |||||
{ | |||||
if (auto* o = getDynamicObject()) | |||||
return o->invokeMethod (method, var::NativeFunctionArgs (*this, arguments, numArguments)); | |||||
return {}; | |||||
} | |||||
var var::call (const Identifier& method) const | |||||
{ | |||||
return invoke (method, nullptr, 0); | |||||
} | |||||
var var::call (const Identifier& method, const var& arg1) const | |||||
{ | |||||
return invoke (method, &arg1, 1); | |||||
} | |||||
var var::call (const Identifier& method, const var& arg1, const var& arg2) const | |||||
{ | |||||
var args[] = { arg1, arg2 }; | |||||
return invoke (method, args, 2); | |||||
} | |||||
var var::call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3) | |||||
{ | |||||
var args[] = { arg1, arg2, arg3 }; | |||||
return invoke (method, args, 3); | |||||
} | |||||
var var::call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3, const var& arg4) const | |||||
{ | |||||
var args[] = { arg1, arg2, arg3, arg4 }; | |||||
return invoke (method, args, 4); | |||||
} | |||||
var var::call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3, const var& arg4, const var& arg5) const | |||||
{ | |||||
var args[] = { arg1, arg2, arg3, arg4, arg5 }; | |||||
return invoke (method, args, 5); | |||||
} | |||||
//============================================================================== | |||||
int var::size() const | |||||
{ | |||||
if (auto* array = getArray()) | |||||
return array->size(); | |||||
return 0; | |||||
} | |||||
const var& var::operator[] (int arrayIndex) const | |||||
{ | |||||
auto* array = getArray(); | |||||
// When using this method, the var must actually be an array, and the index | |||||
// must be in-range! | |||||
jassert (array != nullptr && isPositiveAndBelow (arrayIndex, array->size())); | |||||
return array->getReference (arrayIndex); | |||||
} | |||||
var& var::operator[] (int arrayIndex) | |||||
{ | |||||
auto* array = getArray(); | |||||
// When using this method, the var must actually be an array, and the index | |||||
// must be in-range! | |||||
jassert (array != nullptr && isPositiveAndBelow (arrayIndex, array->size())); | |||||
return array->getReference (arrayIndex); | |||||
} | |||||
Array<var>* var::convertToArray() | |||||
{ | |||||
if (auto* array = getArray()) | |||||
return array; | |||||
Array<var> tempVar; | |||||
if (! isVoid()) | |||||
tempVar.add (*this); | |||||
*this = tempVar; | |||||
return getArray(); | |||||
} | |||||
void var::append (const var& n) | |||||
{ | |||||
convertToArray()->add (n); | |||||
} | |||||
void var::remove (const int index) | |||||
{ | |||||
if (auto* const array = getArray()) | |||||
array->remove (index); | |||||
} | |||||
void var::insert (const int index, const var& n) | |||||
{ | |||||
convertToArray()->insert (index, n); | |||||
} | |||||
void var::resize (const int numArrayElementsWanted) | |||||
{ | |||||
convertToArray()->resize (numArrayElementsWanted); | |||||
} | |||||
int var::indexOf (const var& n) const | |||||
{ | |||||
if (auto* const array = getArray()) | |||||
return array->indexOf (n); | |||||
return -1; | |||||
} | |||||
//============================================================================== | |||||
void var::writeToStream (OutputStream& output) const | |||||
{ | |||||
type->writeToStream (value, output); | |||||
} | |||||
var var::readFromStream (InputStream& input) | |||||
{ | |||||
const int numBytes = input.readCompressedInt(); | |||||
if (numBytes > 0) | |||||
{ | |||||
switch (input.readByte()) | |||||
{ | |||||
case varMarker_Int: return var (input.readInt()); | |||||
case varMarker_Int64: return var (input.readInt64()); | |||||
case varMarker_BoolTrue: return var (true); | |||||
case varMarker_BoolFalse: return var (false); | |||||
case varMarker_Double: return var (input.readDouble()); | |||||
case varMarker_String: | |||||
{ | |||||
MemoryOutputStream mo; | |||||
mo.writeFromInputStream (input, numBytes - 1); | |||||
return var (mo.toUTF8()); | |||||
} | |||||
case varMarker_Binary: | |||||
{ | |||||
MemoryBlock mb ((size_t) numBytes - 1); | |||||
if (numBytes > 1) | |||||
{ | |||||
const int numRead = input.read (mb.getData(), numBytes - 1); | |||||
mb.setSize ((size_t) numRead); | |||||
} | |||||
return var (mb); | |||||
} | |||||
case varMarker_Array: | |||||
{ | |||||
var v; | |||||
auto* destArray = v.convertToArray(); | |||||
for (int i = input.readCompressedInt(); --i >= 0;) | |||||
destArray->add (readFromStream (input)); | |||||
return v; | |||||
} | |||||
default: | |||||
input.skipNextBytes (numBytes - 1); break; | |||||
} | |||||
} | |||||
return {}; | |||||
} | |||||
var::NativeFunctionArgs::NativeFunctionArgs (const var& t, const var* args, int numArgs) noexcept | |||||
: thisObject (t), arguments (args), numArguments (numArgs) | |||||
{} | |||||
} // namespace juce |
@@ -0,0 +1,340 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
A variant class, that can be used to hold a range of primitive values. | |||||
A var object can hold a range of simple primitive values, strings, or | |||||
any kind of ReferenceCountedObject. The var class is intended to act like | |||||
the kind of values used in dynamic scripting languages. | |||||
You can save/load var objects either in a small, proprietary binary format | |||||
using writeToStream()/readFromStream(), or as JSON by using the JSON class. | |||||
@see JSON, DynamicObject | |||||
*/ | |||||
class JUCE_API var | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** This structure is passed to a NativeFunction callback, and contains invocation | |||||
details about the function's arguments and context. | |||||
*/ | |||||
struct JUCE_API NativeFunctionArgs | |||||
{ | |||||
NativeFunctionArgs (const var& thisObject, const var* args, int numArgs) noexcept; | |||||
// Suppress a VS2013 compiler warning | |||||
NativeFunctionArgs& operator= (const NativeFunctionArgs&) = delete; | |||||
const var& thisObject; | |||||
const var* arguments; | |||||
int numArguments; | |||||
}; | |||||
using NativeFunction = std::function<var (const NativeFunctionArgs&)>; | |||||
//============================================================================== | |||||
/** Creates a void variant. */ | |||||
var() noexcept; | |||||
/** Destructor. */ | |||||
~var() noexcept; | |||||
#if JUCE_ALLOW_STATIC_NULL_VARIABLES | |||||
/** A static var object that can be used where you need an empty variant object. */ | |||||
static const var null; | |||||
#endif | |||||
var (const var& valueToCopy); | |||||
var (int value) noexcept; | |||||
var (int64 value) noexcept; | |||||
var (bool value) noexcept; | |||||
var (double value) noexcept; | |||||
var (const char* value); | |||||
var (const wchar_t* value); | |||||
var (const String& value); | |||||
var (const Array<var>& value); | |||||
var (const StringArray& value); | |||||
var (ReferenceCountedObject* object); | |||||
var (NativeFunction method) noexcept; | |||||
var (const void* binaryData, size_t dataSize); | |||||
var (const MemoryBlock& binaryData); | |||||
var& operator= (const var& valueToCopy); | |||||
var& operator= (int value); | |||||
var& operator= (int64 value); | |||||
var& operator= (bool value); | |||||
var& operator= (double value); | |||||
var& operator= (const char* value); | |||||
var& operator= (const wchar_t* value); | |||||
var& operator= (const String& value); | |||||
var& operator= (const MemoryBlock& value); | |||||
var& operator= (const Array<var>& value); | |||||
var& operator= (ReferenceCountedObject* object); | |||||
var& operator= (NativeFunction method); | |||||
var (var&&) noexcept; | |||||
var (String&&); | |||||
var (MemoryBlock&&); | |||||
var (Array<var>&&); | |||||
var& operator= (var&&) noexcept; | |||||
var& operator= (String&&); | |||||
void swapWith (var& other) noexcept; | |||||
/** Returns a var object that can be used where you need the javascript "undefined" value. */ | |||||
static var undefined() noexcept; | |||||
//============================================================================== | |||||
operator int() const noexcept; | |||||
operator int64() const noexcept; | |||||
operator bool() const noexcept; | |||||
operator float() const noexcept; | |||||
operator double() const noexcept; | |||||
operator String() const; | |||||
String toString() const; | |||||
/** If this variant holds an array, this provides access to it. | |||||
NOTE: Beware when you use this - the array pointer is only valid for the lifetime | |||||
of the variant that returned it, so be very careful not to call this method on temporary | |||||
var objects that are the return-value of a function, and which may go out of scope before | |||||
you use the array! | |||||
*/ | |||||
Array<var>* getArray() const noexcept; | |||||
/** If this variant holds a memory block, this provides access to it. | |||||
NOTE: Beware when you use this - the MemoryBlock pointer is only valid for the lifetime | |||||
of the variant that returned it, so be very careful not to call this method on temporary | |||||
var objects that are the return-value of a function, and which may go out of scope before | |||||
you use the MemoryBlock! | |||||
*/ | |||||
MemoryBlock* getBinaryData() const noexcept; | |||||
ReferenceCountedObject* getObject() const noexcept; | |||||
DynamicObject* getDynamicObject() const noexcept; | |||||
//============================================================================== | |||||
bool isVoid() const noexcept; | |||||
bool isUndefined() const noexcept; | |||||
bool isInt() const noexcept; | |||||
bool isInt64() const noexcept; | |||||
bool isBool() const noexcept; | |||||
bool isDouble() const noexcept; | |||||
bool isString() const noexcept; | |||||
bool isObject() const noexcept; | |||||
bool isArray() const noexcept; | |||||
bool isBinaryData() const noexcept; | |||||
bool isMethod() const noexcept; | |||||
/** Returns true if this var has the same value as the one supplied. | |||||
Note that this ignores the type, so a string var "123" and an integer var with the | |||||
value 123 are considered to be equal. | |||||
@see equalsWithSameType | |||||
*/ | |||||
bool equals (const var& other) const noexcept; | |||||
/** Returns true if this var has the same value and type as the one supplied. | |||||
This differs from equals() because e.g. "123" and 123 will be considered different. | |||||
@see equals | |||||
*/ | |||||
bool equalsWithSameType (const var& other) const noexcept; | |||||
/** Returns true if this var has the same type as the one supplied. */ | |||||
bool hasSameTypeAs (const var& other) const noexcept; | |||||
/** Returns a deep copy of this object. | |||||
For simple types this just returns a copy, but if the object contains any arrays | |||||
or DynamicObjects, they will be cloned (recursively). | |||||
*/ | |||||
var clone() const noexcept; | |||||
//============================================================================== | |||||
/** If the var is an array, this returns the number of elements. | |||||
If the var isn't actually an array, this will return 0. | |||||
*/ | |||||
int size() const; | |||||
/** If the var is an array, this can be used to return one of its elements. | |||||
To call this method, you must make sure that the var is actually an array, and | |||||
that the index is a valid number. If these conditions aren't met, behaviour is | |||||
undefined. | |||||
For more control over the array's contents, you can call getArray() and manipulate | |||||
it directly as an Array\<var\>. | |||||
*/ | |||||
const var& operator[] (int arrayIndex) const; | |||||
/** If the var is an array, this can be used to return one of its elements. | |||||
To call this method, you must make sure that the var is actually an array, and | |||||
that the index is a valid number. If these conditions aren't met, behaviour is | |||||
undefined. | |||||
For more control over the array's contents, you can call getArray() and manipulate | |||||
it directly as an Array\<var\>. | |||||
*/ | |||||
var& operator[] (int arrayIndex); | |||||
/** Appends an element to the var, converting it to an array if it isn't already one. | |||||
If the var isn't an array, it will be converted to one, and if its value was non-void, | |||||
this value will be kept as the first element of the new array. The parameter value | |||||
will then be appended to it. | |||||
For more control over the array's contents, you can call getArray() and manipulate | |||||
it directly as an Array\<var\>. | |||||
*/ | |||||
void append (const var& valueToAppend); | |||||
/** Inserts an element to the var, converting it to an array if it isn't already one. | |||||
If the var isn't an array, it will be converted to one, and if its value was non-void, | |||||
this value will be kept as the first element of the new array. The parameter value | |||||
will then be inserted into it. | |||||
For more control over the array's contents, you can call getArray() and manipulate | |||||
it directly as an Array\<var\>. | |||||
*/ | |||||
void insert (int index, const var& value); | |||||
/** If the var is an array, this removes one of its elements. | |||||
If the index is out-of-range or the var isn't an array, nothing will be done. | |||||
For more control over the array's contents, you can call getArray() and manipulate | |||||
it directly as an Array\<var\>. | |||||
*/ | |||||
void remove (int index); | |||||
/** Treating the var as an array, this resizes it to contain the specified number of elements. | |||||
If the var isn't an array, it will be converted to one, and if its value was non-void, | |||||
this value will be kept as the first element of the new array before resizing. | |||||
For more control over the array's contents, you can call getArray() and manipulate | |||||
it directly as an Array\<var\>. | |||||
*/ | |||||
void resize (int numArrayElementsWanted); | |||||
/** If the var is an array, this searches it for the first occurrence of the specified value, | |||||
and returns its index. | |||||
If the var isn't an array, or if the value isn't found, this returns -1. | |||||
*/ | |||||
int indexOf (const var& value) const; | |||||
//============================================================================== | |||||
/** If this variant is an object, this returns one of its properties. */ | |||||
const var& operator[] (const Identifier& propertyName) const; | |||||
/** If this variant is an object, this returns one of its properties. */ | |||||
const var& operator[] (const char* propertyName) const; | |||||
/** If this variant is an object, this returns one of its properties, or a default | |||||
fallback value if the property is not set. */ | |||||
var getProperty (const Identifier& propertyName, const var& defaultReturnValue) const; | |||||
/** Returns true if this variant is an object and if it has the given property. */ | |||||
bool hasProperty (const Identifier& propertyName) const noexcept; | |||||
/** Invokes a named method call with no arguments. */ | |||||
var call (const Identifier& method) const; | |||||
/** Invokes a named method call with one argument. */ | |||||
var call (const Identifier& method, const var& arg1) const; | |||||
/** Invokes a named method call with 2 arguments. */ | |||||
var call (const Identifier& method, const var& arg1, const var& arg2) const; | |||||
/** Invokes a named method call with 3 arguments. */ | |||||
var call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3); | |||||
/** Invokes a named method call with 4 arguments. */ | |||||
var call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3, const var& arg4) const; | |||||
/** Invokes a named method call with 5 arguments. */ | |||||
var call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3, const var& arg4, const var& arg5) const; | |||||
/** Invokes a named method call with a list of arguments. */ | |||||
var invoke (const Identifier& method, const var* arguments, int numArguments) const; | |||||
/** If this object is a method, this returns the function pointer. */ | |||||
NativeFunction getNativeFunction() const; | |||||
//============================================================================== | |||||
/** Writes a binary representation of this value to a stream. | |||||
The data can be read back later using readFromStream(). | |||||
@see JSON | |||||
*/ | |||||
void writeToStream (OutputStream& output) const; | |||||
/** Reads back a stored binary representation of a value. | |||||
The data in the stream must have been written using writeToStream(), or this | |||||
will have unpredictable results. | |||||
@see JSON | |||||
*/ | |||||
static var readFromStream (InputStream& input); | |||||
private: | |||||
//============================================================================== | |||||
class VariantType; friend class VariantType; | |||||
class VariantType_Void; friend class VariantType_Void; | |||||
class VariantType_Undefined; friend class VariantType_Undefined; | |||||
class VariantType_Int; friend class VariantType_Int; | |||||
class VariantType_Int64; friend class VariantType_Int64; | |||||
class VariantType_Double; friend class VariantType_Double; | |||||
class VariantType_Bool; friend class VariantType_Bool; | |||||
class VariantType_String; friend class VariantType_String; | |||||
class VariantType_Object; friend class VariantType_Object; | |||||
class VariantType_Array; friend class VariantType_Array; | |||||
class VariantType_Binary; friend class VariantType_Binary; | |||||
class VariantType_Method; friend class VariantType_Method; | |||||
union ValueUnion | |||||
{ | |||||
int intValue; | |||||
int64 int64Value; | |||||
bool boolValue; | |||||
double doubleValue; | |||||
char stringValue [sizeof (String)]; | |||||
ReferenceCountedObject* objectValue; | |||||
MemoryBlock* binaryValue; | |||||
NativeFunction* methodValue; | |||||
}; | |||||
const VariantType* type; | |||||
ValueUnion value; | |||||
Array<var>* convertToArray(); | |||||
var (const VariantType&) noexcept; | |||||
}; | |||||
/** Compares the values of two var objects, using the var::equals() comparison. */ | |||||
JUCE_API bool operator== (const var&, const var&) noexcept; | |||||
/** Compares the values of two var objects, using the var::equals() comparison. */ | |||||
JUCE_API bool operator!= (const var&, const var&) noexcept; | |||||
JUCE_API bool operator== (const var&, const String&); | |||||
JUCE_API bool operator!= (const var&, const String&); | |||||
JUCE_API bool operator== (const var&, const char*); | |||||
JUCE_API bool operator!= (const var&, const char*); | |||||
//============================================================================== | |||||
/** This template-overloaded class can be used to convert between var and custom types. */ | |||||
template <typename Type> | |||||
struct VariantConverter | |||||
{ | |||||
static Type fromVar (const var& v) { return static_cast<Type> (v); } | |||||
static var toVar (const Type& t) { return t; } | |||||
}; | |||||
/** This template-overloaded class can be used to convert between var and custom types. */ | |||||
template <> | |||||
struct VariantConverter<String> | |||||
{ | |||||
static String fromVar (const var& v) { return v.toString(); } | |||||
static var toVar (const String& s) { return s; } | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,165 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
DirectoryIterator::DirectoryIterator (const File& directory, bool recursive, | |||||
const String& pattern, const int type) | |||||
: wildCards (parseWildcards (pattern)), | |||||
fileFinder (directory, (recursive || wildCards.size() > 1) ? "*" : pattern), | |||||
wildCard (pattern), | |||||
path (File::addTrailingSeparator (directory.getFullPathName())), | |||||
index (-1), | |||||
totalNumFiles (-1), | |||||
whatToLookFor (type), | |||||
isRecursive (recursive), | |||||
hasBeenAdvanced (false) | |||||
{ | |||||
// you have to specify the type of files you're looking for! | |||||
jassert ((type & (File::findFiles | File::findDirectories)) != 0); | |||||
jassert (type > 0 && type <= 7); | |||||
} | |||||
DirectoryIterator::~DirectoryIterator() | |||||
{ | |||||
} | |||||
StringArray DirectoryIterator::parseWildcards (const String& pattern) | |||||
{ | |||||
StringArray s; | |||||
s.addTokens (pattern, ";,", "\"'"); | |||||
s.trim(); | |||||
s.removeEmptyStrings(); | |||||
return s; | |||||
} | |||||
bool DirectoryIterator::fileMatches (const StringArray& wildCards, const String& filename) | |||||
{ | |||||
for (int i = 0; i < wildCards.size(); ++i) | |||||
if (filename.matchesWildcard (wildCards[i], ! File::areFileNamesCaseSensitive())) | |||||
return true; | |||||
return false; | |||||
} | |||||
bool DirectoryIterator::next() | |||||
{ | |||||
return next (nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); | |||||
} | |||||
bool DirectoryIterator::next (bool* const isDirResult, bool* const isHiddenResult, int64* const fileSize, | |||||
Time* const modTime, Time* const creationTime, bool* const isReadOnly) | |||||
{ | |||||
for (;;) | |||||
{ | |||||
hasBeenAdvanced = true; | |||||
if (subIterator != nullptr) | |||||
{ | |||||
if (subIterator->next (isDirResult, isHiddenResult, fileSize, modTime, creationTime, isReadOnly)) | |||||
return true; | |||||
subIterator = nullptr; | |||||
} | |||||
String filename; | |||||
bool isDirectory, isHidden = false, shouldContinue = false; | |||||
while (fileFinder.next (filename, &isDirectory, | |||||
(isHiddenResult != nullptr || (whatToLookFor & File::ignoreHiddenFiles) != 0) ? &isHidden : nullptr, | |||||
fileSize, modTime, creationTime, isReadOnly)) | |||||
{ | |||||
++index; | |||||
if (! filename.containsOnly (".")) | |||||
{ | |||||
bool matches = false; | |||||
if (isDirectory) | |||||
{ | |||||
if (isRecursive && ((whatToLookFor & File::ignoreHiddenFiles) == 0 || ! isHidden)) | |||||
subIterator = new DirectoryIterator (File::createFileWithoutCheckingPath (path + filename), | |||||
true, wildCard, whatToLookFor); | |||||
matches = (whatToLookFor & File::findDirectories) != 0; | |||||
} | |||||
else | |||||
{ | |||||
matches = (whatToLookFor & File::findFiles) != 0; | |||||
} | |||||
// if we're not relying on the OS iterator to do the wildcard match, do it now.. | |||||
if (matches && (isRecursive || wildCards.size() > 1)) | |||||
matches = fileMatches (wildCards, filename); | |||||
if (matches && (whatToLookFor & File::ignoreHiddenFiles) != 0) | |||||
matches = ! isHidden; | |||||
if (matches) | |||||
{ | |||||
currentFile = File::createFileWithoutCheckingPath (path + filename); | |||||
if (isHiddenResult != nullptr) *isHiddenResult = isHidden; | |||||
if (isDirResult != nullptr) *isDirResult = isDirectory; | |||||
return true; | |||||
} | |||||
if (subIterator != nullptr) | |||||
{ | |||||
shouldContinue = true; | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
if (! shouldContinue) | |||||
return false; | |||||
} | |||||
} | |||||
const File& DirectoryIterator::getFile() const | |||||
{ | |||||
if (subIterator != nullptr && subIterator->hasBeenAdvanced) | |||||
return subIterator->getFile(); | |||||
// You need to call DirectoryIterator::next() before asking it for the file that it found! | |||||
jassert (hasBeenAdvanced); | |||||
return currentFile; | |||||
} | |||||
float DirectoryIterator::getEstimatedProgress() const | |||||
{ | |||||
if (totalNumFiles < 0) | |||||
totalNumFiles = File (path).getNumberOfChildFiles (File::findFilesAndDirectories); | |||||
if (totalNumFiles <= 0) | |||||
return 0.0f; | |||||
const float detailedIndex = (subIterator != nullptr) ? index + subIterator->getEstimatedProgress() | |||||
: (float) index; | |||||
return jlimit (0.0f, 1.0f, detailedIndex / totalNumFiles); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,152 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Searches through the files in a directory, returning each file that is found. | |||||
A DirectoryIterator will search through a directory and its subdirectories using | |||||
a wildcard filepattern match. | |||||
If you may be scanning a large number of files, it's usually smarter to use this | |||||
class than File::findChildFiles() because it allows you to stop at any time, rather | |||||
than having to wait for the entire scan to finish before getting the results. | |||||
It also provides an estimate of its progress, using a (highly inaccurate!) algorithm. | |||||
*/ | |||||
class JUCE_API DirectoryIterator | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a DirectoryIterator for a given directory. | |||||
After creating one of these, call its next() method to get the | |||||
first file - e.g. @code | |||||
DirectoryIterator iter (File ("/animals/mooses"), true, "*.moose"); | |||||
while (iter.next()) | |||||
{ | |||||
File theFileItFound (iter.getFile()); | |||||
... etc | |||||
} | |||||
@endcode | |||||
@param directory the directory to search in | |||||
@param isRecursive whether all the subdirectories should also be searched | |||||
@param wildCard the file pattern to match. This may contain multiple patterns | |||||
separated by a semi-colon or comma, e.g. "*.jpg;*.png" | |||||
@param whatToLookFor a value from the File::TypesOfFileToFind enum, specifying | |||||
whether to look for files, directories, or both. | |||||
*/ | |||||
DirectoryIterator (const File& directory, | |||||
bool isRecursive, | |||||
const String& wildCard = "*", | |||||
int whatToLookFor = File::findFiles); | |||||
/** Destructor. */ | |||||
~DirectoryIterator(); | |||||
/** Moves the iterator along to the next file. | |||||
@returns true if a file was found (you can then use getFile() to see what it was) - or | |||||
false if there are no more matching files. | |||||
*/ | |||||
bool next(); | |||||
/** Moves the iterator along to the next file, and returns various properties of that file. | |||||
If you need to find out details about the file, it's more efficient to call this method than | |||||
to call the normal next() method and then find out the details afterwards. | |||||
All the parameters are optional, so pass null pointers for any items that you're not | |||||
interested in. | |||||
@returns true if a file was found (you can then use getFile() to see what it was) - or | |||||
false if there are no more matching files. If it returns false, then none of the | |||||
parameters will be filled-in. | |||||
*/ | |||||
bool next (bool* isDirectory, | |||||
bool* isHidden, | |||||
int64* fileSize, | |||||
Time* modTime, | |||||
Time* creationTime, | |||||
bool* isReadOnly); | |||||
/** Returns the file that the iterator is currently pointing at. | |||||
The result of this call is only valid after a call to next() has returned true. | |||||
*/ | |||||
const File& getFile() const; | |||||
/** Returns a guess of how far through the search the iterator has got. | |||||
@returns a value 0.0 to 1.0 to show the progress, although this won't be | |||||
very accurate. | |||||
*/ | |||||
float getEstimatedProgress() const; | |||||
private: | |||||
//============================================================================== | |||||
class NativeIterator | |||||
{ | |||||
public: | |||||
NativeIterator (const File& directory, const String& wildCard); | |||||
~NativeIterator(); | |||||
bool next (String& filenameFound, | |||||
bool* isDirectory, bool* isHidden, int64* fileSize, | |||||
Time* modTime, Time* creationTime, bool* isReadOnly); | |||||
class Pimpl; | |||||
private: | |||||
friend class DirectoryIterator; | |||||
friend struct ContainerDeletePolicy<Pimpl>; | |||||
ScopedPointer<Pimpl> pimpl; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeIterator) | |||||
}; | |||||
friend struct ContainerDeletePolicy<NativeIterator::Pimpl>; | |||||
StringArray wildCards; | |||||
NativeIterator fileFinder; | |||||
String wildCard, path; | |||||
int index; | |||||
mutable int totalNumFiles; | |||||
const int whatToLookFor; | |||||
const bool isRecursive; | |||||
bool hasBeenAdvanced; | |||||
ScopedPointer<DirectoryIterator> subIterator; | |||||
File currentFile; | |||||
static StringArray parseWildcards (const String& pattern); | |||||
static bool fileMatches (const StringArray& wildCards, const String& filename); | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectoryIterator) | |||||
}; | |||||
} // namespace juce |