@@ -11,6 +11,11 @@ | |||
*.orig | |||
*.rej | |||
*.cmake | |||
CMakeCache.txt | |||
CMakeFiles | |||
Makefile | |||
*.lv2 | |||
*.make | |||
*.make-e | |||
@@ -74,3 +79,4 @@ libs/juce-*/source/modules/*/native/javaopt/ | |||
libs/juce-*/source/modules/*/native/oboe/ | |||
libs/juce-*/source/modules/*/native/*_android_* | |||
libs/juce-*/source/modules/*/native/*_ios_* | |||
libs/juce-*/source/tools |
@@ -12,8 +12,7 @@ JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF MERCHANTABILITY AND FITNESS FOR A | |||
PARTICULAR PURPOSE, ARE DISCLAIMED. | |||
The core JUCE modules (juce_audio_basics, juce_audio_devices, juce_blocks_basics, juce_core | |||
and juce_events) are permissively licensed under the terms of the | |||
[ISC license](http://www.isc.org/downloads/software-support-policy/isc-license/). | |||
The juce_audio_basics, juce_audio_devices, juce_core and juce_events modules | |||
are permissively licensed under the terms of the [ISC license](http://www.isc.org/downloads/software-support-policy/isc-license). | |||
Other modules are covered by the | |||
[GNU General Public License v.3](https://www.gnu.org/licenses/gpl-3.0.en.html). |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -60,29 +60,121 @@ public: | |||
fpsUnknown = 99 | |||
}; | |||
/** More descriptive frame rate type. */ | |||
class JUCE_API FrameRate | |||
{ | |||
public: | |||
/** Creates a frame rate with a base rate of 0. */ | |||
FrameRate() = default; | |||
/** Creates a FrameRate instance from a FrameRateType. */ | |||
FrameRate (FrameRateType type) : FrameRate (fromType (type)) {} | |||
/** Gets the FrameRateType that matches the state of this FrameRate. | |||
Returns fpsUnknown if this FrameRate cannot be represented by any of the | |||
other enum fields. | |||
*/ | |||
FrameRateType getType() const | |||
{ | |||
switch (base) | |||
{ | |||
case 24: return pulldown ? fps23976 : fps24; | |||
case 25: return fps25; | |||
case 30: return pulldown ? (drop ? fps2997drop : fps2997) | |||
: (drop ? fps30drop : fps30); | |||
case 60: return drop ? fps60drop : fps60; | |||
} | |||
return fpsUnknown; | |||
} | |||
/** Returns the plain rate, without taking pulldown into account. */ | |||
int getBaseRate() const { return base; } | |||
/** Returns true if drop-frame timecode is in use. */ | |||
bool isDrop() const { return drop; } | |||
/** Returns true if the effective framerate is actually equal to the base rate divided by 1.001 */ | |||
bool isPullDown() const { return pulldown; } | |||
/** Returns the actual rate described by this object, taking pulldown into account. */ | |||
double getEffectiveRate() const { return pulldown ? (double) base / 1.001 : (double) base; } | |||
/** Returns a copy of this object with the specified base rate. */ | |||
JUCE_NODISCARD FrameRate withBaseRate (int x) const { return with (&FrameRate::base, x); } | |||
/** Returns a copy of this object with drop frames enabled or disabled, as specified. */ | |||
JUCE_NODISCARD FrameRate withDrop (bool x = true) const { return with (&FrameRate::drop, x); } | |||
/** Returns a copy of this object with pulldown enabled or disabled, as specified. */ | |||
JUCE_NODISCARD FrameRate withPullDown (bool x = true) const { return with (&FrameRate::pulldown, x); } | |||
/** Returns true if this instance is equal to other. */ | |||
bool operator== (const FrameRate& other) const | |||
{ | |||
const auto tie = [] (const FrameRate& x) { return std::tie (x.base, x.drop, x.pulldown); }; | |||
return tie (*this) == tie (other); | |||
} | |||
/** Returns true if this instance is not equal to other. */ | |||
bool operator!= (const FrameRate& other) const { return ! (*this == other); } | |||
private: | |||
static FrameRate fromType (FrameRateType type) | |||
{ | |||
switch (type) | |||
{ | |||
case fps23976: return FrameRate().withBaseRate (24).withPullDown(); | |||
case fps24: return FrameRate().withBaseRate (24); | |||
case fps25: return FrameRate().withBaseRate (25); | |||
case fps2997: return FrameRate().withBaseRate (30).withPullDown(); | |||
case fps30: return FrameRate().withBaseRate (30); | |||
case fps2997drop: return FrameRate().withBaseRate (30).withDrop().withPullDown(); | |||
case fps30drop: return FrameRate().withBaseRate (30).withDrop(); | |||
case fps60: return FrameRate().withBaseRate (60); | |||
case fps60drop: return FrameRate().withBaseRate (60).withDrop(); | |||
case fpsUnknown: break; | |||
} | |||
return {}; | |||
} | |||
template <typename Member, typename Value> | |||
FrameRate with (Member&& member, Value&& value) const | |||
{ | |||
auto copy = *this; | |||
copy.*member = std::forward<Value> (value); | |||
return copy; | |||
} | |||
int base = 0; | |||
bool drop = false, pulldown = false; | |||
}; | |||
//============================================================================== | |||
/** This structure is filled-in by the AudioPlayHead::getCurrentPosition() method. | |||
*/ | |||
struct JUCE_API CurrentPositionInfo | |||
{ | |||
/** The tempo in BPM */ | |||
double bpm; | |||
double bpm = 120.0; | |||
/** Time signature numerator, e.g. the 3 of a 3/4 time sig */ | |||
int timeSigNumerator; | |||
int timeSigNumerator = 4; | |||
/** Time signature denominator, e.g. the 4 of a 3/4 time sig */ | |||
int timeSigDenominator; | |||
int timeSigDenominator = 4; | |||
/** The current play position, in samples from the start of the timeline. */ | |||
int64 timeInSamples; | |||
int64 timeInSamples = 0; | |||
/** The current play position, in seconds from the start of the timeline. */ | |||
double timeInSeconds; | |||
double timeInSeconds = 0; | |||
/** For timecode, the position of the start of the timeline, in seconds from 00:00:00:00. */ | |||
double editOriginTime; | |||
double editOriginTime = 0; | |||
/** The current play position, in units of quarter-notes. */ | |||
double ppqPosition; | |||
double ppqPosition = 0; | |||
/** The position of the start of the last bar, in units of quarter-notes. | |||
@@ -92,51 +184,56 @@ public: | |||
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; | |||
double ppqPositionOfLastBarStart = 0; | |||
/** The video frame rate, if applicable. */ | |||
FrameRateType frameRate; | |||
FrameRate frameRate = FrameRateType::fps23976; | |||
/** True if the transport is currently playing. */ | |||
bool isPlaying; | |||
bool isPlaying = false; | |||
/** True if the transport is currently recording. | |||
(When isRecording is true, then isPlaying will also be true). | |||
*/ | |||
bool isRecording; | |||
bool isRecording = false; | |||
/** The current cycle start position in units of quarter-notes. | |||
Note that not all hosts or plugin formats may provide this value. | |||
@see isLooping | |||
*/ | |||
double ppqLoopStart; | |||
double ppqLoopStart = 0; | |||
/** The current cycle end position in units of quarter-notes. | |||
Note that not all hosts or plugin formats may provide this value. | |||
@see isLooping | |||
*/ | |||
double ppqLoopEnd; | |||
double ppqLoopEnd = 0; | |||
/** True if the transport is currently looping. */ | |||
bool isLooping; | |||
bool isLooping = false; | |||
//============================================================================== | |||
bool operator== (const CurrentPositionInfo& other) const noexcept | |||
{ | |||
return timeInSamples == other.timeInSamples | |||
&& ppqPosition == other.ppqPosition | |||
&& editOriginTime == other.editOriginTime | |||
&& ppqPositionOfLastBarStart == other.ppqPositionOfLastBarStart | |||
&& frameRate == other.frameRate | |||
&& isPlaying == other.isPlaying | |||
&& isRecording == other.isRecording | |||
&& bpm == other.bpm | |||
&& timeSigNumerator == other.timeSigNumerator | |||
&& timeSigDenominator == other.timeSigDenominator | |||
&& ppqLoopStart == other.ppqLoopStart | |||
&& ppqLoopEnd == other.ppqLoopEnd | |||
&& isLooping == other.isLooping; | |||
const auto tie = [] (const CurrentPositionInfo& i) | |||
{ | |||
return std::tie (i.timeInSamples, | |||
i.ppqPosition, | |||
i.editOriginTime, | |||
i.ppqPositionOfLastBarStart, | |||
i.frameRate, | |||
i.isPlaying, | |||
i.isRecording, | |||
i.bpm, | |||
i.timeSigNumerator, | |||
i.timeSigDenominator, | |||
i.ppqLoopStart, | |||
i.ppqLoopEnd, | |||
i.isLooping); | |||
}; | |||
return tie (*this) == tie (other); | |||
} | |||
bool operator!= (const CurrentPositionInfo& other) const noexcept | |||
@@ -146,10 +243,7 @@ public: | |||
void resetToDefault() | |||
{ | |||
zerostruct (*this); | |||
timeSigNumerator = 4; | |||
timeSigDenominator = 4; | |||
bpm = 120; | |||
*this = CurrentPositionInfo{}; | |||
} | |||
}; | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -29,7 +29,7 @@ AudioChannelSet::AudioChannelSet (uint32 c) : channels (static_cast<int64> (c)) | |||
{ | |||
} | |||
AudioChannelSet::AudioChannelSet (const Array<ChannelType>& c) | |||
AudioChannelSet::AudioChannelSet (const std::initializer_list<ChannelType>& c) | |||
{ | |||
for (auto channel : c) | |||
addChannel (channel); | |||
@@ -339,6 +339,8 @@ String AudioChannelSet::getDescription() const | |||
if (*this == create5point0()) return "5.0 Surround"; | |||
if (*this == create5point1()) return "5.1 Surround"; | |||
if (*this == create5point1point2()) return "5.1.2 Surround"; | |||
if (*this == create5point1point4()) return "5.1.4 Surround"; | |||
if (*this == create6point0()) return "6.0 Surround"; | |||
if (*this == create6point1()) return "6.1 Surround"; | |||
if (*this == create6point0Music()) return "6.0 (Music) Surround"; | |||
@@ -348,7 +350,11 @@ String AudioChannelSet::getDescription() const | |||
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 == create7point0point4()) return "7.0.4 Surround"; | |||
if (*this == create7point1point2()) return "7.1.2 Surround"; | |||
if (*this == create7point1point4()) return "7.1.4 Surround"; | |||
if (*this == create7point1point6()) return "7.1.6 Surround"; | |||
if (*this == create9point1point6()) return "9.1.6 Surround"; | |||
if (*this == quadraphonic()) return "Quadraphonic"; | |||
if (*this == pentagonal()) return "Pentagonal"; | |||
@@ -442,29 +448,33 @@ void AudioChannelSet::removeChannel (ChannelType newChannel) | |||
} | |||
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::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::create7point0point4() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear) | (1u << topFrontLeft) | (1u << topFrontRight) | (1u << topRearLeft) | (1u << topRearRight)); } | |||
AudioChannelSet AudioChannelSet::create7point1point4() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear) | (1u << topFrontLeft) | (1u << topFrontRight) | (1u << topRearLeft) | (1u << topRearRight)); } | |||
AudioChannelSet AudioChannelSet::mono() { return AudioChannelSet ({ centre }); } | |||
AudioChannelSet AudioChannelSet::stereo() { return AudioChannelSet ({ left, right }); } | |||
AudioChannelSet AudioChannelSet::createLCR() { return AudioChannelSet ({ left, right, centre }); } | |||
AudioChannelSet AudioChannelSet::createLRS() { return AudioChannelSet ({ left, right, surround }); } | |||
AudioChannelSet AudioChannelSet::createLCRS() { return AudioChannelSet ({ left, right, centre, surround }); } | |||
AudioChannelSet AudioChannelSet::create5point0() { return AudioChannelSet ({ left, right, centre, leftSurround, rightSurround }); } | |||
AudioChannelSet AudioChannelSet::create5point1() { return AudioChannelSet ({ left, right, centre, LFE, leftSurround, rightSurround }); } | |||
AudioChannelSet AudioChannelSet::create6point0() { return AudioChannelSet ({ left, right, centre, leftSurround, rightSurround, centreSurround }); } | |||
AudioChannelSet AudioChannelSet::create6point1() { return AudioChannelSet ({ left, right, centre, LFE, leftSurround, rightSurround, centreSurround }); } | |||
AudioChannelSet AudioChannelSet::create6point0Music() { return AudioChannelSet ({ left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide }); } | |||
AudioChannelSet AudioChannelSet::create6point1Music() { return AudioChannelSet ({ left, right, LFE, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide }); } | |||
AudioChannelSet AudioChannelSet::create7point0() { return AudioChannelSet ({ left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear }); } | |||
AudioChannelSet AudioChannelSet::create7point0SDDS() { return AudioChannelSet ({ left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre }); } | |||
AudioChannelSet AudioChannelSet::create7point1() { return AudioChannelSet ({ left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear }); } | |||
AudioChannelSet AudioChannelSet::create7point1SDDS() { return AudioChannelSet ({ left, right, centre, LFE, leftSurround, rightSurround, leftCentre, rightCentre }); } | |||
AudioChannelSet AudioChannelSet::quadraphonic() { return AudioChannelSet ({ left, right, leftSurround, rightSurround }); } | |||
AudioChannelSet AudioChannelSet::pentagonal() { return AudioChannelSet ({ left, right, centre, leftSurroundRear, rightSurroundRear }); } | |||
AudioChannelSet AudioChannelSet::hexagonal() { return AudioChannelSet ({ left, right, centre, centreSurround, leftSurroundRear, rightSurroundRear }); } | |||
AudioChannelSet AudioChannelSet::octagonal() { return AudioChannelSet ({ left, right, centre, leftSurround, rightSurround, centreSurround, wideLeft, wideRight }); } | |||
AudioChannelSet AudioChannelSet::create5point1point2() { return AudioChannelSet ({ left, right, centre, LFE, leftSurround, rightSurround, topSideLeft, topSideRight }); } | |||
AudioChannelSet AudioChannelSet::create5point1point4() { return AudioChannelSet ({ left, right, centre, LFE, leftSurround, rightSurround, topFrontLeft, topFrontRight, topRearLeft, topRearRight }); } | |||
AudioChannelSet AudioChannelSet::create7point0point2() { return AudioChannelSet ({ left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topSideLeft, topSideRight }); } | |||
AudioChannelSet AudioChannelSet::create7point1point2() { return AudioChannelSet ({ left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topSideLeft, topSideRight }); } | |||
AudioChannelSet AudioChannelSet::create7point0point4() { return AudioChannelSet ({ left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topFrontLeft, topFrontRight, topRearLeft, topRearRight }); } | |||
AudioChannelSet AudioChannelSet::create7point1point4() { return AudioChannelSet ({ left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topFrontLeft, topFrontRight, topRearLeft, topRearRight }); } | |||
AudioChannelSet AudioChannelSet::create7point1point6() { return AudioChannelSet ({ left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topFrontLeft, topFrontRight, topSideLeft, topSideRight, topRearLeft, topRearRight }); } | |||
AudioChannelSet AudioChannelSet::create9point1point6() { return AudioChannelSet ({ left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, wideLeft, wideRight, topFrontLeft, topFrontRight, topSideLeft, topSideRight, topRearLeft, topRearRight }); } | |||
AudioChannelSet AudioChannelSet::ambisonic (int order) | |||
{ | |||
@@ -534,49 +544,55 @@ Array<AudioChannelSet> AudioChannelSet::channelSetsWithNumberOfChannels (int num | |||
{ | |||
retval.add (AudioChannelSet::discreteChannels (numChannels)); | |||
if (numChannels == 1) | |||
retval.addArray ([numChannels]() -> Array<AudioChannelSet> | |||
{ | |||
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()); | |||
} | |||
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()); | |||
} | |||
switch (numChannels) | |||
{ | |||
case 1: | |||
return { AudioChannelSet::mono() }; | |||
case 2: | |||
return { AudioChannelSet::stereo() }; | |||
case 3: | |||
return { AudioChannelSet::createLCR(), | |||
AudioChannelSet::createLRS() }; | |||
case 4: | |||
return { AudioChannelSet::quadraphonic(), | |||
AudioChannelSet::createLCRS() }; | |||
case 5: | |||
return { AudioChannelSet::create5point0(), | |||
AudioChannelSet::pentagonal() }; | |||
case 6: | |||
return { AudioChannelSet::create5point1(), | |||
AudioChannelSet::create6point0(), | |||
AudioChannelSet::create6point0Music(), | |||
AudioChannelSet::hexagonal() }; | |||
case 7: | |||
return { AudioChannelSet::create7point0(), | |||
AudioChannelSet::create7point0SDDS(), | |||
AudioChannelSet::create6point1(), | |||
AudioChannelSet::create6point1Music() }; | |||
case 8: | |||
return { AudioChannelSet::create7point1(), | |||
AudioChannelSet::create7point1SDDS(), | |||
AudioChannelSet::octagonal(), | |||
AudioChannelSet::create5point1point2() }; | |||
case 9: | |||
return { AudioChannelSet::create7point0point2() }; | |||
case 10: | |||
return { AudioChannelSet::create5point1point4(), | |||
AudioChannelSet::create7point1point2() }; | |||
case 11: | |||
return { AudioChannelSet::create7point0point4() }; | |||
case 12: | |||
return { AudioChannelSet::create7point1point4() }; | |||
case 14: | |||
return { AudioChannelSet::create7point1point6() }; | |||
case 16: | |||
return { AudioChannelSet::create9point1point6() }; | |||
} | |||
return {}; | |||
}()); | |||
auto order = getAmbisonicOrderForNumChannels (numChannels); | |||
if (order >= 0) | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -196,6 +196,18 @@ public: | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create7point1SDDS(); | |||
/** Creates a set for a 5.1.2 surround setup (left, right, centre, LFE, leftSurround, rightSurround, topSideLeft, topSideRight). | |||
Is equivalent to: kAudioChannelLayoutTag_Atmos_5_1_2 (CoreAudio). | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create5point1point2(); | |||
/** Creates a set for a 5.1.4 surround setup (left, right, centre, LFE, leftSurround, rightSurround, topFrontLeft, topFrontRight, topRearLeft, topRearRight). | |||
Is equivalent to: kAudioChannelLayoutTag_Atmos_5_1_4 (CoreAudio). | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create5point1point4(); | |||
/** 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) | |||
@@ -204,7 +216,7 @@ public: | |||
/** 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) | |||
Is equivalent to: k71_2 (VST), AAX_eStemFormat_7_1_2 (AAX), kAudioChannelLayoutTag_Atmos_7_1_2 (CoreAudio) | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create7point1point2(); | |||
@@ -216,10 +228,27 @@ public: | |||
/** Creates a set for Dolby Atmos 7.1.4 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, LFE, topFrontLeft, topFrontRight, topRearLeft, topRearRight). | |||
Is equivalent to: k71_4 (VST), n/a (AAX), n/a (CoreAudio) | |||
Is equivalent to: k71_4 (VST), n/a (AAX), kAudioChannelLayoutTag_Atmos_7_1_4 (CoreAudio) | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create7point1point4(); | |||
/** Creates a set for Dolby Atmos 7.1.6 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, LFE, topFrontLeft, topFrontRight, topSideLeft, topSideRight, topRearLeft, topRearRight). | |||
Is equivalent to: k71_6 (VST), n/a (AAX), n/a (CoreAudio) | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create7point1point6(); | |||
/** Creates a set for a 9.1.6 surround setup (left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, wideLeft, wideRight, topFrontLeft, topFrontRight, topSideLeft, topSideRight, topRearLeft, topRearRight). | |||
Note that the VST3 layout arranges the front speakers "L Lc C Rc R", but the JUCE layout | |||
uses the arrangement "wideLeft left centre right wideRight". To maintain the relative | |||
positions of the speakers, the channels will be remapped accordingly. This means that the | |||
VST3 host's "L" channel will be received on a JUCE plugin's "wideLeft" channel, the | |||
"Lc" channel will be received on a JUCE plugin's "left" channel, and so on. | |||
Is equivalent to: k91_6 (VST3), kAudioChannelLayoutTag_Atmos_9_1_6 (CoreAudio). | |||
*/ | |||
static AudioChannelSet JUCE_CALLTYPE create9point1point6(); | |||
//============================================================================== | |||
/** Creates a set for quadraphonic surround setup (left, right, leftSurround, rightSurround) | |||
@@ -318,8 +347,8 @@ public: | |||
//============================================================================== | |||
// Used by Dolby Atmos 7.0.2 and 7.1.2 | |||
topSideLeft = 28, /**< Lts (AAX), Tsl (VST) channel for Dolby Atmos. */ | |||
topSideRight = 29, /**< Rts (AAX), Tsr (VST) channel for Dolby Atmos. */ | |||
topSideLeft = 28, /**< Lts (AAX), Tsl (VST), Ltm (AU) channel for Dolby Atmos. */ | |||
topSideRight = 29, /**< Rts (AAX), Tsr (VST), Rtm (AU) channel for Dolby Atmos. */ | |||
//============================================================================== | |||
// Ambisonic ACN formats - all channels are SN3D normalised | |||
@@ -487,7 +516,7 @@ private: | |||
//============================================================================== | |||
explicit AudioChannelSet (uint32); | |||
explicit AudioChannelSet (const Array<ChannelType>&); | |||
explicit AudioChannelSet (const std::initializer_list<ChannelType>&); | |||
//============================================================================== | |||
static int JUCE_CALLTYPE getAmbisonicOrderForNumChannels (int); | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -23,6 +23,9 @@ | |||
namespace juce | |||
{ | |||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") | |||
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) | |||
void AudioDataConverters::convertFloatToInt16LE (const float* source, void* dest, int numSamples, int destBytesPerSample) | |||
{ | |||
auto maxVal = (double) 0x7fff; | |||
@@ -431,35 +434,22 @@ void AudioDataConverters::convertFormatToFloat (DataFormat sourceFormat, const v | |||
//============================================================================== | |||
void AudioDataConverters::interleaveSamples (const float** source, float* dest, int numSamples, int numChannels) | |||
{ | |||
for (int chan = 0; chan < numChannels; ++chan) | |||
{ | |||
auto i = chan; | |||
auto src = source [chan]; | |||
using Format = AudioData::Format<AudioData::Float32, AudioData::NativeEndian>; | |||
for (int j = 0; j < numSamples; ++j) | |||
{ | |||
dest [i] = src [j]; | |||
i += numChannels; | |||
} | |||
} | |||
AudioData::interleaveSamples (AudioData::NonInterleavedSource<Format> { source, numChannels }, | |||
AudioData::InterleavedDest<Format> { dest, numChannels }, | |||
numSamples); | |||
} | |||
void AudioDataConverters::deinterleaveSamples (const float* source, float** dest, int numSamples, int numChannels) | |||
{ | |||
for (int chan = 0; chan < numChannels; ++chan) | |||
{ | |||
auto i = chan; | |||
auto dst = dest [chan]; | |||
using Format = AudioData::Format<AudioData::Float32, AudioData::NativeEndian>; | |||
for (int j = 0; j < numSamples; ++j) | |||
{ | |||
dst [j] = source [i]; | |||
i += numChannels; | |||
} | |||
} | |||
AudioData::deinterleaveSamples (AudioData::InterleavedSource<Format> { source, numChannels }, | |||
AudioData::NonInterleavedDest<Format> { dest, numChannels }, | |||
numSamples); | |||
} | |||
//============================================================================== | |||
//============================================================================== | |||
#if JUCE_UNIT_TESTS | |||
@@ -480,6 +470,7 @@ public: | |||
test (unitTest, true, r); | |||
} | |||
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6262) | |||
static void test (UnitTest& unitTest, bool inPlace, Random& r) | |||
{ | |||
const int numSamples = 2048; | |||
@@ -537,6 +528,7 @@ public: | |||
unitTest.expect (biggestDiff <= errorMargin); | |||
} | |||
} | |||
JUCE_END_IGNORE_WARNINGS_MSVC | |||
}; | |||
template <class F1, class E1, class FormatType> | |||
@@ -586,6 +578,50 @@ public: | |||
Test1 <AudioData::Int32>::test (*this, r); | |||
beginTest ("Round-trip conversion: Float32"); | |||
Test1 <AudioData::Float32>::test (*this, r); | |||
using Format = AudioData::Format<AudioData::Float32, AudioData::NativeEndian>; | |||
beginTest ("Interleaving"); | |||
{ | |||
constexpr auto numChannels = 4; | |||
constexpr auto numSamples = 512; | |||
AudioBuffer<float> sourceBuffer { numChannels, numSamples }, | |||
destBuffer { 1, numChannels * numSamples }; | |||
for (int ch = 0; ch < numChannels; ++ch) | |||
for (int i = 0; i < numSamples; ++i) | |||
sourceBuffer.setSample (ch, i, r.nextFloat()); | |||
AudioData::interleaveSamples (AudioData::NonInterleavedSource<Format> { sourceBuffer.getArrayOfReadPointers(), numChannels }, | |||
AudioData::InterleavedDest<Format> { destBuffer.getWritePointer (0), numChannels }, | |||
numSamples); | |||
for (int ch = 0; ch < numChannels; ++ch) | |||
for (int i = 0; i < numSamples; ++i) | |||
expect (destBuffer.getSample (0, ch + (i * numChannels)) == sourceBuffer.getSample (ch, i)); | |||
} | |||
beginTest ("Deinterleaving"); | |||
{ | |||
constexpr auto numChannels = 4; | |||
constexpr auto numSamples = 512; | |||
AudioBuffer<float> sourceBuffer { 1, numChannels * numSamples }, | |||
destBuffer { numChannels, numSamples }; | |||
for (int ch = 0; ch < numChannels; ++ch) | |||
for (int i = 0; i < numSamples; ++i) | |||
sourceBuffer.setSample (0, ch + (i * numChannels), r.nextFloat()); | |||
AudioData::deinterleaveSamples (AudioData::InterleavedSource<Format> { sourceBuffer.getReadPointer (0), numChannels }, | |||
AudioData::NonInterleavedDest<Format> { destBuffer.getArrayOfWritePointers(), numChannels }, | |||
numSamples); | |||
for (int ch = 0; ch < numChannels; ++ch) | |||
for (int i = 0; i < numSamples; ++i) | |||
expect (sourceBuffer.getSample (0, ch + (i * numChannels)) == destBuffer.getSample (ch, i)); | |||
} | |||
} | |||
}; | |||
@@ -593,4 +629,7 @@ static AudioConversionTests audioConversionUnitTests; | |||
#endif | |||
JUCE_END_IGNORE_WARNINGS_MSVC | |||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
} // namespace juce |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -639,11 +639,152 @@ public: | |||
const int sourceChannels, destChannels; | |||
}; | |||
}; | |||
//============================================================================== | |||
/** A struct that contains a SampleFormat and Endianness to be used with the source and | |||
destination types when calling the interleaveSamples() and deinterleaveSamples() helpers. | |||
@see interleaveSamples, deinterleaveSamples | |||
*/ | |||
template <typename DataFormatIn, typename EndiannessIn> | |||
struct Format | |||
{ | |||
using DataFormat = DataFormatIn; | |||
using Endianness = EndiannessIn; | |||
}; | |||
private: | |||
template <bool IsInterleaved, bool IsConst, typename...> | |||
struct ChannelDataSubtypes; | |||
template <bool IsInterleaved, bool IsConst, typename DataFormat, typename Endianness> | |||
struct ChannelDataSubtypes<IsInterleaved, IsConst, DataFormat, Endianness> | |||
{ | |||
using ElementType = std::remove_pointer_t<decltype (DataFormat::data)>; | |||
using ChannelType = std::conditional_t<IsConst, const ElementType*, ElementType*>; | |||
using DataType = std::conditional_t<IsInterleaved, ChannelType, ChannelType*>; | |||
using PointerType = Pointer<DataFormat, | |||
Endianness, | |||
std::conditional_t<IsInterleaved, Interleaved, NonInterleaved>, | |||
std::conditional_t<IsConst, Const, NonConst>>; | |||
}; | |||
template <bool IsInterleaved, bool IsConst, typename DataFormat, typename Endianness> | |||
struct ChannelDataSubtypes<IsInterleaved, IsConst, Format<DataFormat, Endianness>> | |||
{ | |||
using Subtypes = ChannelDataSubtypes<IsInterleaved, IsConst, DataFormat, Endianness>; | |||
using DataType = typename Subtypes::DataType; | |||
using PointerType = typename Subtypes::PointerType; | |||
}; | |||
template <bool IsInterleaved, bool IsConst, typename... Format> | |||
struct ChannelData | |||
{ | |||
using Subtypes = ChannelDataSubtypes<IsInterleaved, IsConst, Format...>; | |||
using DataType = typename Subtypes::DataType; | |||
using PointerType = typename Subtypes::PointerType; | |||
DataType data; | |||
int channels; | |||
}; | |||
public: | |||
//============================================================================== | |||
/** A sequence of interleaved samples used as the source for the deinterleaveSamples() method. */ | |||
template <typename... Format> using InterleavedSource = ChannelData<true, true, Format...>; | |||
/** A sequence of interleaved samples used as the destination for the interleaveSamples() method. */ | |||
template <typename... Format> using InterleavedDest = ChannelData<true, false, Format...>; | |||
/** A sequence of non-interleaved samples used as the source for the interleaveSamples() method. */ | |||
template <typename... Format> using NonInterleavedSource = ChannelData<false, true, Format...>; | |||
/** A sequence of non-interleaved samples used as the destination for the deinterleaveSamples() method. */ | |||
template <typename... Format> using NonInterleavedDest = ChannelData<false, false, Format...>; | |||
/** A helper function for converting a sequence of samples from a non-interleaved source | |||
to an interleaved destination. | |||
When calling this method you need to specify the source and destination data format and endianness | |||
from the AudioData SampleFormat and Endianness types and provide the data and number of channels | |||
for each. For example, to convert a floating-point stream of big endian samples to an interleaved, | |||
native endian stream of 16-bit integer samples you would do the following: | |||
@code | |||
using SourceFormat = AudioData::Format<AudioData::Float32, AudioData::BigEndian>; | |||
using DestFormat = AudioData::Format<AudioData::Int16, AudioData::NativeEndian>; | |||
AudioData::interleaveSamples (AudioData::NonInterleavedSource<SourceFormat> { sourceData, numSourceChannels }, | |||
AudioData::InterleavedDest<DestFormat> { destData, numDestChannels }, | |||
numSamples); | |||
@endcode | |||
*/ | |||
template <typename... SourceFormat, typename... DestFormat> | |||
static void interleaveSamples (NonInterleavedSource<SourceFormat...> source, | |||
InterleavedDest<DestFormat...> dest, | |||
int numSamples) | |||
{ | |||
using SourceType = typename decltype (source)::PointerType; | |||
using DestType = typename decltype (dest) ::PointerType; | |||
for (int i = 0; i < dest.channels; ++i) | |||
{ | |||
const DestType destType (addBytesToPointer (dest.data, i * DestType::getBytesPerSample()), dest.channels); | |||
if (i < source.channels) | |||
{ | |||
if (*source.data != nullptr) | |||
{ | |||
destType.convertSamples (SourceType { *source.data }, numSamples); | |||
++source.data; | |||
} | |||
} | |||
else | |||
{ | |||
destType.clearSamples (numSamples); | |||
} | |||
} | |||
} | |||
/** A helper function for converting a sequence of samples from an interleaved source | |||
to a non-interleaved destination. | |||
When calling this method you need to specify the source and destination data format and endianness | |||
from the AudioData SampleFormat and Endianness types and provide the data and number of channels | |||
for each. For example, to convert a floating-point stream of big endian samples to an non-interleaved, | |||
native endian stream of 16-bit integer samples you would do the following: | |||
@code | |||
using SourceFormat = AudioData::Format<AudioData::Float32, AudioData::BigEndian>; | |||
using DestFormat = AudioData::Format<AudioData::Int16, AudioData::NativeEndian>; | |||
AudioData::deinterleaveSamples (AudioData::InterleavedSource<SourceFormat> { sourceData, numSourceChannels }, | |||
AudioData::NonInterleavedDest<DestFormat> { destData, numDestChannels }, | |||
numSamples); | |||
@endcode | |||
*/ | |||
template <typename... SourceFormat, typename... DestFormat> | |||
static void deinterleaveSamples (InterleavedSource<SourceFormat...> source, | |||
NonInterleavedDest<DestFormat...> dest, | |||
int numSamples) | |||
{ | |||
using SourceType = typename decltype (source)::PointerType; | |||
using DestType = typename decltype (dest) ::PointerType; | |||
for (int i = 0; i < dest.channels; ++i) | |||
{ | |||
if (auto* targetChan = dest.data[i]) | |||
{ | |||
const DestType destType (targetChan); | |||
if (i < source.channels) | |||
destType.convertSamples (SourceType (addBytesToPointer (source.data, i * SourceType::getBytesPerSample()), source.channels), numSamples); | |||
else | |||
destType.clearSamples (numSamples); | |||
} | |||
} | |||
} | |||
}; | |||
//============================================================================== | |||
#ifndef DOXYGEN | |||
/** | |||
A set of routines to convert buffers of 32-bit floating point data to and from | |||
various integer formats. | |||
@@ -653,7 +794,7 @@ public: | |||
@tags{Audio} | |||
*/ | |||
class JUCE_API AudioDataConverters | |||
class [[deprecated]] JUCE_API AudioDataConverters | |||
{ | |||
public: | |||
//============================================================================== | |||
@@ -710,7 +851,7 @@ public: | |||
private: | |||
AudioDataConverters(); | |||
JUCE_DECLARE_NON_COPYABLE (AudioDataConverters) | |||
}; | |||
#endif | |||
} // namespace juce |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -23,8 +23,8 @@ | |||
namespace juce | |||
{ | |||
AudioProcessLoadMeasurer::AudioProcessLoadMeasurer() {} | |||
AudioProcessLoadMeasurer::~AudioProcessLoadMeasurer() {} | |||
AudioProcessLoadMeasurer::AudioProcessLoadMeasurer() = default; | |||
AudioProcessLoadMeasurer::~AudioProcessLoadMeasurer() = default; | |||
void AudioProcessLoadMeasurer::reset() | |||
{ | |||
@@ -33,43 +33,67 @@ void AudioProcessLoadMeasurer::reset() | |||
void AudioProcessLoadMeasurer::reset (double sampleRate, int blockSize) | |||
{ | |||
cpuUsageMs = 0; | |||
const SpinLock::ScopedLockType lock (mutex); | |||
cpuUsageProportion = 0; | |||
xruns = 0; | |||
if (sampleRate > 0.0 && blockSize > 0) | |||
{ | |||
msPerBlock = 1000.0 * blockSize / sampleRate; | |||
timeToCpuScale = (msPerBlock > 0.0) ? (1.0 / msPerBlock) : 0.0; | |||
} | |||
else | |||
{ | |||
msPerBlock = 0; | |||
timeToCpuScale = 0; | |||
} | |||
samplesPerBlock = blockSize; | |||
msPerSample = (sampleRate > 0.0 && blockSize > 0) ? 1000.0 / sampleRate : 0; | |||
} | |||
void AudioProcessLoadMeasurer::registerBlockRenderTime (double milliseconds) | |||
{ | |||
const double filterAmount = 0.2; | |||
cpuUsageMs += filterAmount * (milliseconds - cpuUsageMs); | |||
const SpinLock::ScopedTryLockType lock (mutex); | |||
if (lock.isLocked()) | |||
registerRenderTimeLocked (milliseconds, samplesPerBlock); | |||
} | |||
void AudioProcessLoadMeasurer::registerRenderTime (double milliseconds, int numSamples) | |||
{ | |||
const SpinLock::ScopedTryLockType lock (mutex); | |||
if (lock.isLocked()) | |||
registerRenderTimeLocked (milliseconds, numSamples); | |||
} | |||
if (milliseconds > msPerBlock) | |||
void AudioProcessLoadMeasurer::registerRenderTimeLocked (double milliseconds, int numSamples) | |||
{ | |||
if (msPerSample == 0) | |||
return; | |||
const auto maxMilliseconds = numSamples * msPerSample; | |||
const auto usedProportion = milliseconds / maxMilliseconds; | |||
const auto filterAmount = 0.2; | |||
const auto proportion = cpuUsageProportion.load(); | |||
cpuUsageProportion = proportion + filterAmount * (usedProportion - proportion); | |||
if (milliseconds > maxMilliseconds) | |||
++xruns; | |||
} | |||
double AudioProcessLoadMeasurer::getLoadAsProportion() const { return jlimit (0.0, 1.0, timeToCpuScale * cpuUsageMs); } | |||
double AudioProcessLoadMeasurer::getLoadAsProportion() const { return jlimit (0.0, 1.0, cpuUsageProportion.load()); } | |||
double AudioProcessLoadMeasurer::getLoadAsPercentage() const { return 100.0 * getLoadAsProportion(); } | |||
int AudioProcessLoadMeasurer::getXRunCount() const { return xruns; } | |||
AudioProcessLoadMeasurer::ScopedTimer::ScopedTimer (AudioProcessLoadMeasurer& p) | |||
: owner (p), startTime (Time::getMillisecondCounterHiRes()) | |||
: ScopedTimer (p, p.samplesPerBlock) | |||
{ | |||
} | |||
AudioProcessLoadMeasurer::ScopedTimer::ScopedTimer (AudioProcessLoadMeasurer& p, int numSamplesInBlock) | |||
: owner (p), startTime (Time::getMillisecondCounterHiRes()), samplesInBlock (numSamplesInBlock) | |||
{ | |||
// numSamplesInBlock should never be zero. Did you remember to call AudioProcessLoadMeasurer::reset(), | |||
// passing the expected samples per block? | |||
jassert (numSamplesInBlock); | |||
} | |||
AudioProcessLoadMeasurer::ScopedTimer::~ScopedTimer() | |||
{ | |||
owner.registerBlockRenderTime (Time::getMillisecondCounterHiRes() - startTime); | |||
owner.registerRenderTime (Time::getMillisecondCounterHiRes() - startTime, samplesInBlock); | |||
} | |||
} // namespace juce |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -72,11 +72,13 @@ public: | |||
struct JUCE_API ScopedTimer | |||
{ | |||
ScopedTimer (AudioProcessLoadMeasurer&); | |||
ScopedTimer (AudioProcessLoadMeasurer&, int numSamplesInBlock); | |||
~ScopedTimer(); | |||
private: | |||
AudioProcessLoadMeasurer& owner; | |||
double startTime; | |||
int samplesInBlock; | |||
JUCE_DECLARE_NON_COPYABLE (ScopedTimer) | |||
}; | |||
@@ -87,9 +89,20 @@ public: | |||
*/ | |||
void registerBlockRenderTime (double millisecondsTaken); | |||
/** Can be called manually to add the time of a callback to the stats. | |||
Normally you probably would never call this - it's simpler and more robust to | |||
use a ScopedTimer to measure the time using an RAII pattern. | |||
*/ | |||
void registerRenderTime (double millisecondsTaken, int numSamples); | |||
private: | |||
double cpuUsageMs = 0, timeToCpuScale = 0, msPerBlock = 0; | |||
int xruns = 0; | |||
void registerRenderTimeLocked (double, int); | |||
SpinLock mutex; | |||
int samplesPerBlock = 0; | |||
double msPerSample = 0; | |||
std::atomic<double> cpuUsageProportion { 0 }; | |||
std::atomic<int> xruns { 0 }; | |||
}; | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -20,11 +20,6 @@ | |||
============================================================================== | |||
*/ | |||
#ifndef __clang__ | |||
// GCC4 compatibility | |||
namespace std { using ::max_align_t; } | |||
#endif | |||
namespace juce | |||
{ | |||
@@ -197,6 +192,7 @@ public: | |||
} | |||
/** Copies another buffer onto this one. | |||
This buffer's size will be changed to that of the other buffer. | |||
*/ | |||
AudioBuffer& operator= (const AudioBuffer& other) | |||
@@ -222,17 +218,18 @@ public: | |||
} | |||
/** Destructor. | |||
This will free any memory allocated by the buffer. | |||
*/ | |||
~AudioBuffer() = default; | |||
/** Move constructor */ | |||
/** Move constructor. */ | |||
AudioBuffer (AudioBuffer&& other) noexcept | |||
: numChannels (other.numChannels), | |||
size (other.size), | |||
allocatedBytes (other.allocatedBytes), | |||
allocatedData (std::move (other.allocatedData)), | |||
isClear (other.isClear.load()) | |||
isClear (other.isClear) | |||
{ | |||
if (numChannels < (int) numElementsInArray (preallocatedChannelSpace)) | |||
{ | |||
@@ -251,14 +248,14 @@ public: | |||
other.allocatedBytes = 0; | |||
} | |||
/** Move assignment */ | |||
/** Move assignment. */ | |||
AudioBuffer& operator= (AudioBuffer&& other) noexcept | |||
{ | |||
numChannels = other.numChannels; | |||
size = other.size; | |||
allocatedBytes = other.allocatedBytes; | |||
allocatedData = std::move (other.allocatedData); | |||
isClear = other.isClear.load(); | |||
isClear = other.isClear; | |||
if (numChannels < (int) numElementsInArray (preallocatedChannelSpace)) | |||
{ | |||
@@ -280,18 +277,22 @@ public: | |||
//============================================================================== | |||
/** Returns the number of channels of audio data that this buffer contains. | |||
@see getNumSamples, getReadPointer, getWritePointer | |||
*/ | |||
int getNumChannels() const noexcept { return numChannels; } | |||
/** Returns the number of samples allocated in each of the buffer's channels. | |||
@see getNumChannels, getReadPointer, getWritePointer | |||
*/ | |||
int getNumSamples() const noexcept { return size; } | |||
/** Returns a pointer to an array of read-only samples in one of the buffer's channels. | |||
For speed, this doesn't check whether the channel number is out of range, | |||
so be careful when using it! | |||
If you need to write to the data, do NOT call this method and const_cast the | |||
result! Instead, you must call getWritePointer so that the buffer knows you're | |||
planning on modifying the data. | |||
@@ -303,8 +304,10 @@ public: | |||
} | |||
/** Returns a pointer to an array of read-only samples in one of the buffer's channels. | |||
For speed, this doesn't check whether the channel number or index are out of range, | |||
so be careful when using it! | |||
If you need to write to the data, do NOT call this method and const_cast the | |||
result! Instead, you must call getWritePointer so that the buffer knows you're | |||
planning on modifying the data. | |||
@@ -317,10 +320,20 @@ public: | |||
} | |||
/** Returns a writeable pointer to one of the buffer's channels. | |||
For speed, this doesn't check whether the channel number is out of range, | |||
so be careful when using it! | |||
Note that if you're not planning on writing to the data, you should always | |||
use getReadPointer instead. | |||
This will mark the buffer as not cleared and the hasBeenCleared method will return | |||
false after this call. If you retain this write pointer and write some data to | |||
the buffer after calling its clear method, subsequent clear calls will do nothing. | |||
To avoid this either call this method each time you need to write data, or use the | |||
setNotClear method to force the internal cleared flag to false. | |||
@see setNotClear | |||
*/ | |||
Type* getWritePointer (int channelNumber) noexcept | |||
{ | |||
@@ -330,10 +343,20 @@ public: | |||
} | |||
/** Returns a writeable pointer to one of the buffer's channels. | |||
For speed, this doesn't check whether the channel number or index are out of range, | |||
so be careful when using it! | |||
Note that if you're not planning on writing to the data, you should | |||
use getReadPointer instead. | |||
This will mark the buffer as not cleared and the hasBeenCleared method will return | |||
false after this call. If you retain this write pointer and write some data to | |||
the buffer after calling its clear method, subsequent clear calls will do nothing. | |||
To avoid this either call this method each time you need to write data, or use the | |||
setNotClear method to force the internal cleared flag to false. | |||
@see setNotClear | |||
*/ | |||
Type* getWritePointer (int channelNumber, int sampleIndex) noexcept | |||
{ | |||
@@ -354,6 +377,14 @@ public: | |||
Don't modify any of the pointers that are returned, and bear in mind that | |||
these will become invalid if the buffer is resized. | |||
This will mark the buffer as not cleared and the hasBeenCleared method will return | |||
false after this call. If you retain this write pointer and write some data to | |||
the buffer after calling its clear method, subsequent clear calls will do nothing. | |||
To avoid this either call this method each time you need to write data, or use the | |||
setNotClear method to force the internal cleared flag to false. | |||
@see setNotClear | |||
*/ | |||
Type** getArrayOfWritePointers() noexcept { isClear = false; return channels; } | |||
@@ -362,23 +393,23 @@ public: | |||
This can expand or contract the buffer's length, and add or remove channels. | |||
If keepExistingContent is true, it will try to preserve as much of the | |||
old data as it can in the new buffer. | |||
If clearExtraSpace is true, then any extra channels or space that is | |||
allocated will be also be cleared. If false, then this space is left | |||
uninitialised. | |||
If avoidReallocating is true, then changing the buffer's size won't reduce the | |||
amount of memory that is currently allocated (but it will still increase it if | |||
the new size is bigger than the amount it currently has). If this is false, then | |||
a new allocation will be done so that the buffer uses takes up the minimum amount | |||
of memory that it needs. | |||
Note that if keepExistingContent and avoidReallocating are both true, then it will | |||
only avoid reallocating if neither the channel count or length in samples increase. | |||
If the required memory can't be allocated, this will throw a std::bad_alloc exception. | |||
@param newNumChannels the new number of channels. | |||
@param newNumSamples the new number of samples. | |||
@param keepExistingContent if this is true, it will try to preserve as much of the | |||
old data as it can in the new buffer. | |||
@param clearExtraSpace if this is true, then any extra channels or space that is | |||
allocated will be also be cleared. If false, then this space is left | |||
uninitialised. | |||
@param avoidReallocating if this is true, then changing the buffer's size won't reduce the | |||
amount of memory that is currently allocated (but it will still | |||
increase it if the new size is bigger than the amount it currently has). | |||
If this is false, then a new allocation will be done so that the buffer | |||
uses takes up the minimum amount of memory that it needs. | |||
*/ | |||
void setSize (int newNumChannels, | |||
int newNumSamples, | |||
@@ -469,6 +500,8 @@ public: | |||
will re-allocate memory internally and copy the existing data to this new area, | |||
so it will then stop directly addressing this memory. | |||
The hasBeenCleared method will return false after this call. | |||
@param dataToReferTo a pre-allocated array containing pointers to the data | |||
for each channel that should be used by this buffer. The | |||
buffer will only refer to this memory, it won't try to delete | |||
@@ -509,6 +542,8 @@ public: | |||
will re-allocate memory internally and copy the existing data to this new area, | |||
so it will then stop directly addressing this memory. | |||
The hasBeenCleared method will return false after this call. | |||
@param dataToReferTo a pre-allocated array containing pointers to the data | |||
for each channel that should be used by this buffer. The | |||
buffer will only refer to this memory, it won't try to delete | |||
@@ -526,8 +561,12 @@ public: | |||
} | |||
/** Resizes this buffer to match the given one, and copies all of its content across. | |||
The source buffer can contain a different floating point type, so this can be used to | |||
convert between 32 and 64 bit float buffer types. | |||
The hasBeenCleared method will return false after this call if the other buffer | |||
contains data. | |||
*/ | |||
template <typename OtherType> | |||
void makeCopyOf (const AudioBuffer<OtherType>& other, bool avoidReallocating = false) | |||
@@ -554,7 +593,13 @@ public: | |||
} | |||
//============================================================================== | |||
/** Clears all the samples in all channels. */ | |||
/** Clears all the samples in all channels and marks the buffer as cleared. | |||
This method will do nothing if the buffer has been marked as cleared (i.e. the | |||
hasBeenCleared method returns true.) | |||
@see hasBeenCleared, setNotClear | |||
*/ | |||
void clear() noexcept | |||
{ | |||
if (! isClear) | |||
@@ -568,8 +613,15 @@ public: | |||
/** Clears a specified region of all the channels. | |||
This will mark the buffer as cleared if the entire buffer contents are cleared. | |||
For speed, this doesn't check whether the channel and sample number | |||
are in-range, so be careful! | |||
This method will do nothing if the buffer has been marked as cleared (i.e. the | |||
hasBeenCleared method returns true.) | |||
@see hasBeenCleared, setNotClear | |||
*/ | |||
void clear (int startSample, int numSamples) noexcept | |||
{ | |||
@@ -577,11 +629,10 @@ public: | |||
if (! isClear) | |||
{ | |||
if (startSample == 0 && numSamples == size) | |||
isClear = true; | |||
for (int i = 0; i < numChannels; ++i) | |||
FloatVectorOperations::clear (channels[i] + startSample, numSamples); | |||
isClear = (startSample == 0 && numSamples == size); | |||
} | |||
} | |||
@@ -589,6 +640,11 @@ public: | |||
For speed, this doesn't check whether the channel and sample number | |||
are in-range, so be careful! | |||
This method will do nothing if the buffer has been marked as cleared (i.e. the | |||
hasBeenCleared method returns true.) | |||
@see hasBeenCleared, setNotClear | |||
*/ | |||
void clear (int channel, int startSample, int numSamples) noexcept | |||
{ | |||
@@ -600,15 +656,26 @@ public: | |||
} | |||
/** Returns true if the buffer has been entirely cleared. | |||
Note that this does not actually measure the contents of the buffer - it simply | |||
returns a flag that is set when the buffer is cleared, and which is reset whenever | |||
functions like getWritePointer() are invoked. That means the method does not take | |||
any time, but it may return false negatives when in fact the buffer is still empty. | |||
functions like getWritePointer are invoked. That means the method is quick, but it | |||
may return false negatives when in fact the buffer is still empty. | |||
*/ | |||
bool hasBeenCleared() const noexcept { return isClear; } | |||
/** Forces the internal cleared flag of the buffer to false. | |||
This may be useful in the case where you are holding on to a write pointer and call | |||
the clear method before writing some data. You can then use this method to mark the | |||
buffer as containing data so that subsequent clear calls will succeed. However a | |||
better solution is to call getWritePointer each time you need to write data. | |||
*/ | |||
void setNotClear() noexcept { isClear = false; } | |||
//============================================================================== | |||
/** Returns a sample from the buffer. | |||
The channel and index are not checked - they are expected to be in-range. If not, | |||
an assertion will be thrown, but in a release build, you're into 'undefined behaviour' | |||
territory. | |||
@@ -621,9 +688,12 @@ public: | |||
} | |||
/** Sets a sample in the buffer. | |||
The channel and index are not checked - they are expected to be in-range. If not, | |||
an assertion will be thrown, but in a release build, you're into 'undefined behaviour' | |||
territory. | |||
The hasBeenCleared method will return false after this call. | |||
*/ | |||
void setSample (int destChannel, int destSample, Type newValue) noexcept | |||
{ | |||
@@ -634,9 +704,12 @@ public: | |||
} | |||
/** Adds a value to a sample in the buffer. | |||
The channel and index are not checked - they are expected to be in-range. If not, | |||
an assertion will be thrown, but in a release build, you're into 'undefined behaviour' | |||
territory. | |||
The hasBeenCleared method will return false after this call. | |||
*/ | |||
void addSample (int destChannel, int destSample, Type valueToAdd) noexcept | |||
{ | |||
@@ -737,6 +810,9 @@ public: | |||
/** Adds samples from another buffer to this one. | |||
The hasBeenCleared method will return false after this call if samples have | |||
been added. | |||
@param destChannel the channel within this buffer to add the samples to | |||
@param destStartSample the start sample within this buffer's channel | |||
@param source the source buffer to add from | |||
@@ -756,7 +832,10 @@ public: | |||
int numSamples, | |||
Type gainToApplyToSource = Type (1)) noexcept | |||
{ | |||
jassert (&source != this || sourceChannel != destChannel); | |||
jassert (&source != this | |||
|| sourceChannel != destChannel | |||
|| sourceStartSample + numSamples <= destStartSample | |||
|| destStartSample + numSamples <= sourceStartSample); | |||
jassert (isPositiveAndBelow (destChannel, numChannels)); | |||
jassert (destStartSample >= 0 && numSamples >= 0 && destStartSample + numSamples <= size); | |||
jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); | |||
@@ -788,6 +867,9 @@ public: | |||
/** Adds samples from an array of floats to one of the channels. | |||
The hasBeenCleared method will return false after this call if samples have | |||
been added. | |||
@param destChannel the channel within this buffer to add the samples to | |||
@param destStartSample the start sample within this buffer's channel | |||
@param source the source data to use | |||
@@ -833,6 +915,9 @@ public: | |||
/** Adds samples from an array of floats, applying a gain ramp to them. | |||
The hasBeenCleared method will return false after this call if samples have | |||
been added. | |||
@param destChannel the channel within this buffer to add the samples to | |||
@param destStartSample the start sample within this buffer's channel | |||
@param source the source data to use | |||
@@ -892,7 +977,10 @@ public: | |||
int sourceStartSample, | |||
int numSamples) noexcept | |||
{ | |||
jassert (&source != this || sourceChannel != destChannel); | |||
jassert (&source != this | |||
|| sourceChannel != destChannel | |||
|| sourceStartSample + numSamples <= destStartSample | |||
|| destStartSample + numSamples <= sourceStartSample); | |||
jassert (isPositiveAndBelow (destChannel, numChannels)); | |||
jassert (destStartSample >= 0 && destStartSample + numSamples <= size); | |||
jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); | |||
@@ -917,6 +1005,9 @@ public: | |||
/** Copies samples from an array of floats into one of the channels. | |||
The hasBeenCleared method will return false after this call if samples have | |||
been copied. | |||
@param destChannel the channel within this buffer to copy the samples to | |||
@param destStartSample the start sample within this buffer's channel | |||
@param source the source buffer to read from | |||
@@ -942,6 +1033,9 @@ public: | |||
/** Copies samples from an array of floats into one of the channels, applying a gain to it. | |||
The hasBeenCleared method will return false after this call if samples have | |||
been copied. | |||
@param destChannel the channel within this buffer to copy the samples to | |||
@param destStartSample the start sample within this buffer's channel | |||
@param source the source buffer to read from | |||
@@ -987,6 +1081,9 @@ public: | |||
/** Copies samples from an array of floats into one of the channels, applying a gain ramp. | |||
The hasBeenCleared method will return false after this call if samples have | |||
been copied. | |||
@param destChannel the channel within this buffer to copy the samples to | |||
@param destStartSample the start sample within this buffer's channel | |||
@param source the source buffer to read from | |||
@@ -1123,11 +1220,11 @@ private: | |||
Type** channels; | |||
HeapBlock<char, true> allocatedData; | |||
Type* preallocatedChannelSpace[32]; | |||
std::atomic<bool> isClear { false }; | |||
bool isClear = false; | |||
void allocateData() | |||
{ | |||
#if ! JUCE_PROJUCER_LIVE_BUILD && (! JUCE_GCC || (__GNUC__ * 100 + __GNUC_MINOR__) >= 409) | |||
#if (! JUCE_GCC || (__GNUC__ * 100 + __GNUC_MINOR__) >= 409) | |||
static_assert (alignof (Type) <= detail::maxAlignment, | |||
"AudioBuffer cannot hold types with alignment requirements larger than that guaranteed by malloc"); | |||
#endif | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -34,185 +34,197 @@ class ScopedNoDenormals; | |||
//============================================================================== | |||
/** | |||
A collection of simple vector operations on arrays of floats, accelerated with | |||
SIMD instructions where possible. | |||
A collection of simple vector operations on arrays of floating point numbers, | |||
accelerated with SIMD instructions where possible, usually accessed from | |||
the FloatVectorOperations class. | |||
@tags{Audio} | |||
*/ | |||
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; | |||
@code | |||
float data[64]; | |||
/** Copies a repeated value into a vector of doubles. */ | |||
static void JUCE_CALLTYPE fill (double* dest, double valueToFill, int numValues) noexcept; | |||
// The following two function calls are equivalent: | |||
FloatVectorOperationsBase<float, int>::clear (data, 64); | |||
FloatVectorOperations::clear (data, 64); | |||
@endcode | |||
/** Copies a vector of floats. */ | |||
static void JUCE_CALLTYPE copy (float* dest, const float* src, int numValues) noexcept; | |||
@see FloatVectorOperations | |||
/** Copies a vector of doubles. */ | |||
static void JUCE_CALLTYPE copy (double* dest, const double* src, int numValues) noexcept; | |||
@tags{Audio} | |||
*/ | |||
template <typename FloatType, typename CountType> | |||
struct FloatVectorOperationsBase | |||
{ | |||
/** Clears a vector of floating point numbers. */ | |||
static void JUCE_CALLTYPE clear (FloatType* dest, CountType 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 repeated value into a vector of floating point numbers. */ | |||
static void JUCE_CALLTYPE fill (FloatType* dest, FloatType valueToFill, CountType 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; | |||
/** Copies a vector of floating point numbers. */ | |||
static void JUCE_CALLTYPE copy (FloatType* dest, const FloatType* src, CountType numValues) noexcept; | |||
/** Adds a fixed value to the destination values. */ | |||
static void JUCE_CALLTYPE add (float* dest, float amountToAdd, int numValues) noexcept; | |||
/** Copies a vector of floating point numbers, multiplying each value by a given multiplier */ | |||
static void JUCE_CALLTYPE copyWithMultiply (FloatType* dest, const FloatType* src, FloatType multiplier, CountType 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; | |||
static void JUCE_CALLTYPE add (FloatType* dest, FloatType amountToAdd, CountType 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; | |||
static void JUCE_CALLTYPE add (FloatType* dest, const FloatType* src, FloatType amount, CountType 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; | |||
static void JUCE_CALLTYPE add (FloatType* dest, const FloatType* src, CountType numValues) 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; | |||
static void JUCE_CALLTYPE add (FloatType* dest, const FloatType* src1, const FloatType* src2, CountType num) noexcept; | |||
/** Subtracts the source values from the destination values. */ | |||
static void JUCE_CALLTYPE subtract (double* dest, const double* src, int numValues) noexcept; | |||
static void JUCE_CALLTYPE subtract (FloatType* dest, const FloatType* src, CountType 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; | |||
static void JUCE_CALLTYPE subtract (FloatType* dest, const FloatType* src1, const FloatType* src2, CountType 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; | |||
static void JUCE_CALLTYPE addWithMultiply (FloatType* dest, const FloatType* src, FloatType multiplier, CountType 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; | |||
static void JUCE_CALLTYPE addWithMultiply (FloatType* dest, const FloatType* src1, const FloatType* src2, CountType num) 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; | |||
static void JUCE_CALLTYPE subtractWithMultiply (FloatType* dest, const FloatType* src, FloatType multiplier, CountType 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; | |||
static void JUCE_CALLTYPE subtractWithMultiply (FloatType* dest, const FloatType* src1, const FloatType* src2, CountType 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; | |||
static void JUCE_CALLTYPE multiply (FloatType* dest, const FloatType* src, CountType 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; | |||
static void JUCE_CALLTYPE multiply (FloatType* dest, const FloatType* src1, const FloatType* src2, CountType numValues) noexcept; | |||
/** Multiplies each of the destination values by a fixed multiplier. */ | |||
static void JUCE_CALLTYPE multiply (double* dest, double multiplier, int numValues) noexcept; | |||
static void JUCE_CALLTYPE multiply (FloatType* dest, FloatType multiplier, CountType 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; | |||
static void JUCE_CALLTYPE multiply (FloatType* dest, const FloatType* src, FloatType multiplier, CountType num) noexcept; | |||
/** Copies a source vector to a destination, negating each value. */ | |||
static void JUCE_CALLTYPE negate (double* dest, const double* src, int numValues) noexcept; | |||
static void JUCE_CALLTYPE negate (FloatType* dest, const FloatType* src, CountType 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; | |||
static void JUCE_CALLTYPE abs (FloatType* dest, const FloatType* src, CountType 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; | |||
/** 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 (FloatType* dest, const FloatType* src, FloatType comp, CountType num) 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 source1 and source2 values. */ | |||
static void JUCE_CALLTYPE min (FloatType* dest, const FloatType* src1, const FloatType* src2, CountType 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 (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 (FloatType* dest, const FloatType* src, FloatType comp, CountType 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 maximum of the corresponding source1 and source2 values. */ | |||
static void JUCE_CALLTYPE max (FloatType* dest, const FloatType* src1, const FloatType* src2, CountType 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 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 (FloatType* dest, const FloatType* src, FloatType low, FloatType high, CountType 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; | |||
/** Finds the minimum and maximum values in the given array. */ | |||
static Range<FloatType> JUCE_CALLTYPE findMinAndMax (const FloatType* src, CountType numValues) 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; | |||
/** Finds the minimum value in the given array. */ | |||
static FloatType JUCE_CALLTYPE findMinimum (const FloatType* src, CountType numValues) 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; | |||
/** Finds the maximum value in the given array. */ | |||
static FloatType JUCE_CALLTYPE findMaximum (const FloatType* src, CountType numValues) 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; | |||
#if ! DOXYGEN | |||
namespace detail | |||
{ | |||
/** 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; | |||
template <typename...> | |||
struct NameForwarder; | |||
/** 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; | |||
template <typename Head> | |||
struct NameForwarder<Head> : Head {}; | |||
/** 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; | |||
template <typename Head, typename... Tail> | |||
struct NameForwarder<Head, Tail...> : Head, NameForwarder<Tail...> | |||
{ | |||
using Head::clear; | |||
using NameForwarder<Tail...>::clear; | |||
/** Finds the minimum and maximum values in the given array. */ | |||
static Range<float> JUCE_CALLTYPE findMinAndMax (const float* src, int numValues) noexcept; | |||
using Head::fill; | |||
using NameForwarder<Tail...>::fill; | |||
/** Finds the minimum and maximum values in the given array. */ | |||
static Range<double> JUCE_CALLTYPE findMinAndMax (const double* src, int numValues) noexcept; | |||
using Head::copy; | |||
using NameForwarder<Tail...>::copy; | |||
/** Finds the minimum value in the given array. */ | |||
static float JUCE_CALLTYPE findMinimum (const float* src, int numValues) noexcept; | |||
using Head::copyWithMultiply; | |||
using NameForwarder<Tail...>::copyWithMultiply; | |||
/** Finds the minimum value in the given array. */ | |||
static double JUCE_CALLTYPE findMinimum (const double* src, int numValues) noexcept; | |||
using Head::add; | |||
using NameForwarder<Tail...>::add; | |||
/** Finds the maximum value in the given array. */ | |||
static float JUCE_CALLTYPE findMaximum (const float* src, int numValues) noexcept; | |||
using Head::subtract; | |||
using NameForwarder<Tail...>::subtract; | |||
/** Finds the maximum value in the given array. */ | |||
static double JUCE_CALLTYPE findMaximum (const double* src, int numValues) noexcept; | |||
using Head::addWithMultiply; | |||
using NameForwarder<Tail...>::addWithMultiply; | |||
using Head::subtractWithMultiply; | |||
using NameForwarder<Tail...>::subtractWithMultiply; | |||
using Head::multiply; | |||
using NameForwarder<Tail...>::multiply; | |||
using Head::negate; | |||
using NameForwarder<Tail...>::negate; | |||
using Head::abs; | |||
using NameForwarder<Tail...>::abs; | |||
using Head::min; | |||
using NameForwarder<Tail...>::min; | |||
using Head::max; | |||
using NameForwarder<Tail...>::max; | |||
using Head::clip; | |||
using NameForwarder<Tail...>::clip; | |||
using Head::findMinAndMax; | |||
using NameForwarder<Tail...>::findMinAndMax; | |||
using Head::findMinimum; | |||
using NameForwarder<Tail...>::findMinimum; | |||
using Head::findMaximum; | |||
using NameForwarder<Tail...>::findMaximum; | |||
}; | |||
} // namespace detail | |||
#endif | |||
//============================================================================== | |||
/** | |||
A collection of simple vector operations on arrays of floating point numbers, | |||
accelerated with SIMD instructions where possible and providing all methods | |||
from FloatVectorOperationsBase. | |||
@see FloatVectorOperationsBase | |||
@tags{Audio} | |||
*/ | |||
class JUCE_API FloatVectorOperations : public detail::NameForwarder<FloatVectorOperationsBase<float, int>, | |||
FloatVectorOperationsBase<float, size_t>, | |||
FloatVectorOperationsBase<double, int>, | |||
FloatVectorOperationsBase<double, size_t>> | |||
{ | |||
public: | |||
static void JUCE_CALLTYPE convertFixedToFloat (float* dest, const int* src, float multiplier, int num) noexcept; | |||
static void JUCE_CALLTYPE convertFixedToFloat (float* dest, const int* src, float multiplier, size_t num) noexcept; | |||
/** This method enables or disables the SSE/NEON flush-to-zero mode. */ | |||
static void JUCE_CALLTYPE enableFlushToZeroMode (bool shouldEnable) noexcept; | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -31,6 +31,8 @@ | |||
#include "juce_audio_basics.h" | |||
#include <juce_core/containers/juce_Optional.h> | |||
#if JUCE_MINGW && ! defined (alloca) | |||
#define alloca __builtin_alloca | |||
#endif | |||
@@ -86,3 +88,14 @@ | |||
#include "sources/juce_ReverbAudioSource.cpp" | |||
#include "sources/juce_ToneGeneratorAudioSource.cpp" | |||
#include "synthesisers/juce_Synthesiser.cpp" | |||
#include "midi/ump/juce_UMP.h" | |||
#include "midi/ump/juce_UMPUtils.cpp" | |||
#include "midi/ump/juce_UMPView.cpp" | |||
#include "midi/ump/juce_UMPSysEx7.cpp" | |||
#include "midi/ump/juce_UMPMidi1ToMidi2DefaultTranslator.cpp" | |||
#if JUCE_UNIT_TESTS | |||
#include "utilities/juce_ADSR_test.cpp" | |||
#include "midi/ump/juce_UMP_test.cpp" | |||
#endif |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -32,11 +32,12 @@ | |||
ID: juce_audio_basics | |||
vendor: juce | |||
version: 6.0.7 | |||
version: 6.1.6 | |||
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 | |||
minimumCppStandard: 14 | |||
dependencies: juce_core | |||
OSXFrameworks: Accelerate | |||
@@ -119,4 +120,4 @@ | |||
#include "sources/juce_ReverbAudioSource.h" | |||
#include "sources/juce_ToneGeneratorAudioSource.h" | |||
#include "synthesisers/juce_Synthesiser.h" | |||
#include "audio_play_head/juce_AudioPlayHead.h" | |||
#include "audio_play_head/juce_AudioPlayHead.h" |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -119,29 +119,37 @@ void MidiBuffer::clear (int startSample, int numSamples) | |||
data.removeRange ((int) (start - data.begin()), (int) (end - start)); | |||
} | |||
void MidiBuffer::addEvent (const MidiMessage& m, int sampleNumber) | |||
bool MidiBuffer::addEvent (const MidiMessage& m, int sampleNumber) | |||
{ | |||
addEvent (m.getRawData(), m.getRawDataSize(), sampleNumber); | |||
return addEvent (m.getRawData(), m.getRawDataSize(), sampleNumber); | |||
} | |||
void MidiBuffer::addEvent (const void* newData, int maxBytes, int sampleNumber) | |||
bool MidiBuffer::addEvent (const void* newData, int maxBytes, int sampleNumber) | |||
{ | |||
auto numBytes = MidiBufferHelpers::findActualEventLength (static_cast<const uint8*> (newData), maxBytes); | |||
if (numBytes > 0) | |||
if (numBytes <= 0) | |||
return true; | |||
if (std::numeric_limits<uint16>::max() < numBytes) | |||
{ | |||
auto newItemSize = (size_t) numBytes + sizeof (int32) + sizeof (uint16); | |||
auto offset = (int) (MidiBufferHelpers::findEventAfter (data.begin(), data.end(), sampleNumber) - data.begin()); | |||
// This method only supports messages smaller than (1 << 16) bytes | |||
return false; | |||
} | |||
data.insertMultiple (offset, 0, (int) newItemSize); | |||
auto newItemSize = (size_t) numBytes + sizeof (int32) + sizeof (uint16); | |||
auto offset = (int) (MidiBufferHelpers::findEventAfter (data.begin(), data.end(), sampleNumber) - data.begin()); | |||
auto* d = data.begin() + offset; | |||
writeUnaligned<int32> (d, sampleNumber); | |||
d += sizeof (int32); | |||
writeUnaligned<uint16> (d, static_cast<uint16> (numBytes)); | |||
d += sizeof (uint16); | |||
memcpy (d, newData, (size_t) numBytes); | |||
} | |||
data.insertMultiple (offset, 0, (int) newItemSize); | |||
auto* d = data.begin() + offset; | |||
writeUnaligned<int32> (d, sampleNumber); | |||
d += sizeof (int32); | |||
writeUnaligned<uint16> (d, static_cast<uint16> (numBytes)); | |||
d += sizeof (uint16); | |||
memcpy (d, newData, (size_t) numBytes); | |||
return true; | |||
} | |||
void MidiBuffer::addEvents (const MidiBuffer& otherBuffer, | |||
@@ -201,13 +209,14 @@ MidiBufferIterator MidiBuffer::findNextSamplePosition (int samplePosition) const | |||
} | |||
//============================================================================== | |||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") | |||
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) | |||
MidiBuffer::Iterator::Iterator (const MidiBuffer& b) noexcept | |||
: buffer (b), iterator (b.data.begin()) | |||
{ | |||
} | |||
MidiBuffer::Iterator::~Iterator() noexcept {} | |||
void MidiBuffer::Iterator::setNextSamplePosition (int samplePosition) noexcept | |||
{ | |||
iterator = buffer.findNextSamplePosition (samplePosition); | |||
@@ -236,6 +245,9 @@ bool MidiBuffer::Iterator::getNextEvent (MidiMessage& result, int& samplePositio | |||
return true; | |||
} | |||
JUCE_END_IGNORE_WARNINGS_MSVC | |||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
//============================================================================== | |||
//============================================================================== | |||
#if JUCE_UNIT_TESTS | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -184,9 +184,11 @@ public: | |||
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 MidiBufferIterator object | |||
To retrieve events, use a MidiBufferIterator object. | |||
Returns true on success, or false on failure. | |||
*/ | |||
void addEvent (const MidiMessage& midiMessage, int sampleNumber); | |||
bool addEvent (const MidiMessage& midiMessage, int sampleNumber); | |||
/** Adds an event to the buffer from raw midi data. | |||
@@ -202,9 +204,11 @@ public: | |||
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 MidiBufferIterator object | |||
To retrieve events, use a MidiBufferIterator object. | |||
Returns true on success, or false on failure. | |||
*/ | |||
void addEvent (const void* rawMidiData, | |||
bool addEvent (const void* rawMidiData, | |||
int maxBytesOfMidiData, | |||
int sampleNumber); | |||
@@ -269,7 +273,9 @@ public: | |||
MidiBufferIterator findNextSamplePosition (int samplePosition) const noexcept; | |||
//============================================================================== | |||
/** | |||
#ifndef DOXYGEN | |||
/** This class is now deprecated in favour of MidiBufferIterator. | |||
Used to iterate through the events in a MidiBuffer. | |||
Note that altering the buffer while an iterator is using it will produce | |||
@@ -277,20 +283,12 @@ public: | |||
@see MidiBuffer | |||
*/ | |||
class JUCE_API Iterator | |||
class [[deprecated]] JUCE_API Iterator | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Creates an Iterator for this MidiBuffer. | |||
This class has been deprecated in favour of MidiBufferIterator. | |||
*/ | |||
JUCE_DEPRECATED (Iterator (const MidiBuffer&) noexcept); | |||
/** Creates a copy of an iterator. */ | |||
Iterator (const Iterator&) = default; | |||
/** Destructor. */ | |||
~Iterator() noexcept; | |||
/** Creates an Iterator for this MidiBuffer. */ | |||
Iterator (const MidiBuffer& b) noexcept; | |||
//============================================================================== | |||
/** Repositions the iterator so that the next event retrieved will be the first | |||
@@ -332,6 +330,7 @@ public: | |||
const MidiBuffer& buffer; | |||
MidiBufferIterator iterator; | |||
}; | |||
#endif | |||
/** The raw data holding this buffer. | |||
Obviously access to this data is provided at your own risk. Its internal format could | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -46,18 +46,6 @@ namespace MidiFileHelpers | |||
} | |||
} | |||
template <typename Value> | |||
struct Optional | |||
{ | |||
Optional() = default; | |||
Optional (const Value& v) | |||
: value (v), valid (true) {} | |||
Value value = Value(); | |||
bool valid = false; | |||
}; | |||
template <typename Integral> | |||
struct ReadTrait; | |||
@@ -100,23 +88,23 @@ namespace MidiFileHelpers | |||
auto ch = tryRead<uint32> (data, remaining); | |||
if (! ch.valid) | |||
if (! ch.hasValue()) | |||
return {}; | |||
if (ch.value != ByteOrder::bigEndianInt ("MThd")) | |||
if (*ch != ByteOrder::bigEndianInt ("MThd")) | |||
{ | |||
auto ok = false; | |||
if (ch.value == ByteOrder::bigEndianInt ("RIFF")) | |||
if (*ch == ByteOrder::bigEndianInt ("RIFF")) | |||
{ | |||
for (int i = 0; i < 8; ++i) | |||
{ | |||
ch = tryRead<uint32> (data, remaining); | |||
if (! ch.valid) | |||
if (! ch.hasValue()) | |||
return {}; | |||
if (ch.value == ByteOrder::bigEndianInt ("MThd")) | |||
if (*ch == ByteOrder::bigEndianInt ("MThd")) | |||
{ | |||
ok = true; | |||
break; | |||
@@ -130,29 +118,29 @@ namespace MidiFileHelpers | |||
const auto bytesRemaining = tryRead<uint32> (data, remaining); | |||
if (! bytesRemaining.valid || bytesRemaining.value > remaining) | |||
if (! bytesRemaining.hasValue() || *bytesRemaining > remaining) | |||
return {}; | |||
const auto optFileType = tryRead<uint16> (data, remaining); | |||
if (! optFileType.valid || 2 < optFileType.value) | |||
if (! optFileType.hasValue() || 2 < *optFileType) | |||
return {}; | |||
const auto optNumTracks = tryRead<uint16> (data, remaining); | |||
if (! optNumTracks.valid || (optFileType.value == 0 && optNumTracks.value != 1)) | |||
if (! optNumTracks.hasValue() || (*optFileType == 0 && *optNumTracks != 1)) | |||
return {}; | |||
const auto optTimeFormat = tryRead<uint16> (data, remaining); | |||
if (! optTimeFormat.valid) | |||
if (! optTimeFormat.hasValue()) | |||
return {}; | |||
HeaderDetails result; | |||
result.fileType = (short) optFileType.value; | |||
result.timeFormat = (short) optTimeFormat.value; | |||
result.numberOfTracks = (short) optNumTracks.value; | |||
result.fileType = (short) *optFileType; | |||
result.timeFormat = (short) *optTimeFormat; | |||
result.numberOfTracks = (short) *optNumTracks; | |||
result.bytesRead = maxSize - remaining; | |||
return { result }; | |||
@@ -264,7 +252,6 @@ namespace MidiFileHelpers | |||
//============================================================================== | |||
MidiFile::MidiFile() : timeFormat ((short) (unsigned short) 0xe728) {} | |||
MidiFile::~MidiFile() {} | |||
MidiFile::MidiFile (const MidiFile& other) : timeFormat (other.timeFormat) | |||
{ | |||
@@ -356,7 +343,9 @@ double MidiFile::getLastTimestamp() const | |||
} | |||
//============================================================================== | |||
bool MidiFile::readFrom (InputStream& sourceStream, bool createMatchingNoteOffs) | |||
bool MidiFile::readFrom (InputStream& sourceStream, | |||
bool createMatchingNoteOffs, | |||
int* fileType) | |||
{ | |||
clear(); | |||
MemoryBlock data; | |||
@@ -372,10 +361,10 @@ bool MidiFile::readFrom (InputStream& sourceStream, bool createMatchingNoteOffs) | |||
const auto optHeader = MidiFileHelpers::parseMidiHeader (d, size); | |||
if (! optHeader.valid) | |||
if (! optHeader.hasValue()) | |||
return false; | |||
const auto header = optHeader.value; | |||
const auto header = *optHeader; | |||
timeFormat = header.timeFormat; | |||
d += header.bytesRead; | |||
@@ -385,27 +374,32 @@ bool MidiFile::readFrom (InputStream& sourceStream, bool createMatchingNoteOffs) | |||
{ | |||
const auto optChunkType = MidiFileHelpers::tryRead<uint32> (d, size); | |||
if (! optChunkType.valid) | |||
if (! optChunkType.hasValue()) | |||
return false; | |||
const auto optChunkSize = MidiFileHelpers::tryRead<uint32> (d, size); | |||
if (! optChunkSize.valid) | |||
if (! optChunkSize.hasValue()) | |||
return false; | |||
const auto chunkSize = optChunkSize.value; | |||
const auto chunkSize = *optChunkSize; | |||
if (size < chunkSize) | |||
return false; | |||
if (optChunkType.value == ByteOrder::bigEndianInt ("MTrk")) | |||
if (*optChunkType == ByteOrder::bigEndianInt ("MTrk")) | |||
readNextTrack (d, (int) chunkSize, createMatchingNoteOffs); | |||
size -= chunkSize; | |||
d += chunkSize; | |||
} | |||
return size == 0; | |||
const auto successful = (size == 0); | |||
if (successful && fileType != nullptr) | |||
*fileType = header.fileType; | |||
return successful; | |||
} | |||
void MidiFile::readNextTrack (const uint8* data, int size, bool createMatchingNoteOffs) | |||
@@ -604,7 +598,7 @@ struct MidiFileTest : public UnitTest | |||
{ | |||
// No data | |||
const auto header = parseHeader ([] (OutputStream&) {}); | |||
expect (! header.valid); | |||
expect (! header.hasValue()); | |||
} | |||
{ | |||
@@ -614,7 +608,7 @@ struct MidiFileTest : public UnitTest | |||
writeBytes (os, { 0xff }); | |||
}); | |||
expect (! header.valid); | |||
expect (! header.hasValue()); | |||
} | |||
{ | |||
@@ -624,7 +618,7 @@ struct MidiFileTest : public UnitTest | |||
writeBytes (os, { 'M', 'T', 'h', 'd' }); | |||
}); | |||
expect (! header.valid); | |||
expect (! header.hasValue()); | |||
} | |||
{ | |||
@@ -634,7 +628,7 @@ struct MidiFileTest : public UnitTest | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 0, 0, 16, 0, 1 }); | |||
}); | |||
expect (! header.valid); | |||
expect (! header.hasValue()); | |||
} | |||
{ | |||
@@ -644,7 +638,7 @@ struct MidiFileTest : public UnitTest | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 5, 0, 16, 0, 1 }); | |||
}); | |||
expect (! header.valid); | |||
expect (! header.hasValue()); | |||
} | |||
{ | |||
@@ -654,12 +648,12 @@ struct MidiFileTest : public UnitTest | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 16, 0, 1 }); | |||
}); | |||
expect (header.valid); | |||
expect (header.hasValue()); | |||
expectEquals (header.value.fileType, (short) 1); | |||
expectEquals (header.value.numberOfTracks, (short) 16); | |||
expectEquals (header.value.timeFormat, (short) 1); | |||
expectEquals ((int) header.value.bytesRead, 14); | |||
expectEquals (header->fileType, (short) 1); | |||
expectEquals (header->numberOfTracks, (short) 16); | |||
expectEquals (header->timeFormat, (short) 1); | |||
expectEquals ((int) header->bytesRead, 14); | |||
} | |||
} | |||
@@ -668,7 +662,7 @@ struct MidiFileTest : public UnitTest | |||
{ | |||
// Empty input | |||
const auto file = parseFile ([] (OutputStream&) {}); | |||
expect (! file.valid); | |||
expect (! file.hasValue()); | |||
} | |||
{ | |||
@@ -678,7 +672,7 @@ struct MidiFileTest : public UnitTest | |||
writeBytes (os, { 'M', 'T', 'h', 'd' }); | |||
}); | |||
expect (! file.valid); | |||
expect (! file.hasValue()); | |||
} | |||
{ | |||
@@ -688,8 +682,8 @@ struct MidiFileTest : public UnitTest | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 0, 0, 1 }); | |||
}); | |||
expect (file.valid); | |||
expectEquals (file.value.getNumTracks(), 0); | |||
expect (file.hasValue()); | |||
expectEquals (file->getNumTracks(), 0); | |||
} | |||
{ | |||
@@ -700,7 +694,7 @@ struct MidiFileTest : public UnitTest | |||
writeBytes (os, { 'M', 'T', 'r', '?' }); | |||
}); | |||
expect (! file.valid); | |||
expect (! file.hasValue()); | |||
} | |||
{ | |||
@@ -711,9 +705,9 @@ struct MidiFileTest : public UnitTest | |||
writeBytes (os, { 'M', 'T', 'r', 'k', 0, 0, 0, 1, 0xff }); | |||
}); | |||
expect (file.valid); | |||
expectEquals (file.value.getNumTracks(), 1); | |||
expectEquals (file.value.getTrack (0)->getNumEvents(), 0); | |||
expect (file.hasValue()); | |||
expectEquals (file->getNumTracks(), 1); | |||
expectEquals (file->getTrack (0)->getNumEvents(), 0); | |||
} | |||
{ | |||
@@ -724,7 +718,7 @@ struct MidiFileTest : public UnitTest | |||
writeBytes (os, { 'M', 'T', 'r', 'k', 0x0f, 0, 0, 0, 0xff }); | |||
}); | |||
expect (! file.valid); | |||
expect (! file.hasValue()); | |||
} | |||
{ | |||
@@ -738,10 +732,10 @@ struct MidiFileTest : public UnitTest | |||
writeBytes (os, { 0x80, 0x00, 0x00 }); | |||
}); | |||
expect (file.valid); | |||
expectEquals (file.value.getNumTracks(), 1); | |||
expect (file.hasValue()); | |||
expectEquals (file->getNumTracks(), 1); | |||
auto& track = *file.value.getTrack (0); | |||
auto& track = *file->getTrack (0); | |||
expectEquals (track.getNumEvents(), 1); | |||
expect (track.getEventPointer (0)->message.isNoteOff()); | |||
expectEquals (track.getEventPointer (0)->message.getTimeStamp(), (double) 0x0f); | |||
@@ -760,7 +754,7 @@ struct MidiFileTest : public UnitTest | |||
} | |||
template <typename Fn> | |||
static MidiFileHelpers::Optional<MidiFileHelpers::HeaderDetails> parseHeader (Fn&& fn) | |||
static Optional<MidiFileHelpers::HeaderDetails> parseHeader (Fn&& fn) | |||
{ | |||
MemoryOutputStream os; | |||
fn (os); | |||
@@ -770,7 +764,7 @@ struct MidiFileTest : public UnitTest | |||
} | |||
template <typename Fn> | |||
static MidiFileHelpers::Optional<MidiFile> parseFile (Fn&& fn) | |||
static Optional<MidiFile> parseFile (Fn&& fn) | |||
{ | |||
MemoryOutputStream os; | |||
fn (os); | |||
@@ -778,7 +772,9 @@ struct MidiFileTest : public UnitTest | |||
MemoryInputStream is (os.getData(), os.getDataSize(), false); | |||
MidiFile mf; | |||
if (mf.readFrom (is)) | |||
int fileType = 0; | |||
if (mf.readFrom (is, true, &fileType)) | |||
return mf; | |||
return {}; | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -45,9 +45,6 @@ public: | |||
/** Creates an empty MidiFile object. */ | |||
MidiFile(); | |||
/** Destructor. */ | |||
~MidiFile(); | |||
/** Creates a copy of another MidiFile. */ | |||
MidiFile (const MidiFile&); | |||
@@ -136,7 +133,7 @@ public: | |||
*/ | |||
void findAllTimeSigEvents (MidiMessageSequence& timeSigEvents) const; | |||
/** Makes a list of all the time-signature meta-events from all tracks in the midi file. | |||
/** Makes a list of all the key-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; | |||
@@ -160,10 +157,14 @@ public: | |||
@param createMatchingNoteOffs if true, any missing note-offs for previous note-ons will | |||
be automatically added at the end of the file by calling | |||
MidiMessageSequence::updateMatchedPairs on each track. | |||
@param midiFileType if not nullptr, the integer at this address will be set | |||
to 0, 1, or 2 depending on the type of the midi file | |||
@returns true if the stream was read successfully | |||
*/ | |||
bool readFrom (InputStream& sourceStream, bool createMatchingNoteOffs = true); | |||
bool readFrom (InputStream& sourceStream, | |||
bool createMatchingNoteOffs = true, | |||
int* midiFileType = nullptr); | |||
/** 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 | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -28,10 +28,6 @@ MidiKeyboardState::MidiKeyboardState() | |||
zerostruct (noteStates); | |||
} | |||
MidiKeyboardState::~MidiKeyboardState() | |||
{ | |||
} | |||
//============================================================================== | |||
void MidiKeyboardState::reset() | |||
{ | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -43,7 +43,6 @@ class JUCE_API MidiKeyboardState | |||
public: | |||
//============================================================================== | |||
MidiKeyboardState(); | |||
~MidiKeyboardState(); | |||
//============================================================================== | |||
/** Resets the state of the object. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -287,11 +287,14 @@ MidiMessage& MidiMessage::operator= (const MidiMessage& other) | |||
{ | |||
if (other.isHeapAllocated()) | |||
{ | |||
if (isHeapAllocated()) | |||
packedData.allocatedData = static_cast<uint8*> (std::realloc (packedData.allocatedData, (size_t) other.size)); | |||
else | |||
packedData.allocatedData = static_cast<uint8*> (std::malloc ((size_t) other.size)); | |||
auto* newStorage = static_cast<uint8*> (isHeapAllocated() | |||
? std::realloc (packedData.allocatedData, (size_t) other.size) | |||
: std::malloc ((size_t) other.size)); | |||
if (newStorage == nullptr) | |||
throw std::bad_alloc{}; // The midi message has not been adjusted at this point | |||
packedData.allocatedData = newStorage; | |||
memcpy (packedData.allocatedData, other.packedData.allocatedData, (size_t) other.size); | |||
} | |||
else | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -102,7 +102,8 @@ public: | |||
double timeStamp = 0, | |||
bool sysexHasEmbeddedLength = true); | |||
/** Creates an active-sense message. | |||
/** Creates an empty sysex message. | |||
Since the MidiMessage has to contain a valid message, this default constructor | |||
just initialises it with an empty sysex message. | |||
*/ | |||
@@ -856,17 +857,16 @@ public: | |||
//============================================================================== | |||
#ifndef DOXYGEN | |||
/** Reads a midi variable-length integer. | |||
This signature has been deprecated in favour of the safer | |||
readVariableLengthValue. | |||
The `data` argument indicates the data to read the number from, | |||
and `numBytesUsed` is used as an out-parameter to indicate the number | |||
of bytes that were read. | |||
*/ | |||
JUCE_DEPRECATED (static int readVariableLengthVal (const uint8* data, | |||
int& numBytesUsed) noexcept); | |||
[[deprecated ("This signature has been deprecated in favour of the safer readVariableLengthValue.")]] | |||
static int readVariableLengthVal (const uint8* data, int& numBytesUsed) noexcept; | |||
#endif | |||
/** Holds information about a variable-length value which was parsed | |||
from a stream of bytes. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -25,7 +25,6 @@ namespace juce | |||
MidiMessageSequence::MidiEventHolder::MidiEventHolder (const MidiMessage& mm) : message (mm) {} | |||
MidiMessageSequence::MidiEventHolder::MidiEventHolder (MidiMessage&& mm) : message (std::move (mm)) {} | |||
MidiMessageSequence::MidiEventHolder::~MidiEventHolder() {} | |||
//============================================================================== | |||
MidiMessageSequence::MidiMessageSequence() | |||
@@ -63,10 +62,6 @@ MidiMessageSequence& MidiMessageSequence::operator= (MidiMessageSequence&& other | |||
return *this; | |||
} | |||
MidiMessageSequence::~MidiMessageSequence() | |||
{ | |||
} | |||
void MidiMessageSequence::swapWith (MidiMessageSequence& other) noexcept | |||
{ | |||
list.swapWith (other.list); | |||
@@ -309,43 +304,174 @@ void MidiMessageSequence::deleteSysExMessages() | |||
} | |||
//============================================================================== | |||
void MidiMessageSequence::createControllerUpdatesForTime (int channelNumber, double time, Array<MidiMessage>& dest) | |||
class OptionalPitchWheel | |||
{ | |||
bool doneProg = false; | |||
bool donePitchWheel = false; | |||
bool doneControllers[128] = {}; | |||
Optional<int> value; | |||
for (int i = list.size(); --i >= 0;) | |||
public: | |||
void emit (int channel, Array<MidiMessage>& out) const | |||
{ | |||
if (value.hasValue()) | |||
out.add (MidiMessage::pitchWheel (channel, *value)); | |||
} | |||
void set (int v) | |||
{ | |||
auto& mm = list.getUnchecked(i)->message; | |||
value = v; | |||
} | |||
}; | |||
if (mm.isForChannel (channelNumber) && mm.getTimeStamp() <= time) | |||
class OptionalControllerValues | |||
{ | |||
Optional<char> values[128]; | |||
public: | |||
void emit (int channel, Array<MidiMessage>& out) const | |||
{ | |||
for (auto it = std::begin (values); it != std::end (values); ++it) | |||
if (it->hasValue()) | |||
out.add (MidiMessage::controllerEvent (channel, (int) std::distance (std::begin (values), it), **it)); | |||
} | |||
void set (int controller, int value) | |||
{ | |||
values[controller] = (char) value; | |||
} | |||
}; | |||
class OptionalProgramChange | |||
{ | |||
Optional<char> value, bankLSB, bankMSB; | |||
public: | |||
void emit (int channel, double time, Array<MidiMessage>& out) const | |||
{ | |||
if (! value.hasValue()) | |||
return; | |||
if (bankLSB.hasValue() && bankMSB.hasValue()) | |||
{ | |||
if (mm.isProgramChange() && ! doneProg) | |||
{ | |||
doneProg = true; | |||
dest.add (MidiMessage (mm, 0.0)); | |||
} | |||
else if (mm.isPitchWheel() && ! donePitchWheel) | |||
out.add (MidiMessage::controllerEvent (channel, 0x00, *bankMSB).withTimeStamp (time)); | |||
out.add (MidiMessage::controllerEvent (channel, 0x20, *bankLSB).withTimeStamp (time)); | |||
} | |||
out.add (MidiMessage::programChange (channel, *value).withTimeStamp (time)); | |||
} | |||
// Returns true if this is a bank number change, and false otherwise. | |||
bool trySetBank (int controller, int v) | |||
{ | |||
switch (controller) | |||
{ | |||
case 0x00: bankMSB = (char) v; return true; | |||
case 0x20: bankLSB = (char) v; return true; | |||
} | |||
return false; | |||
} | |||
void setProgram (int v) { value = (char) v; } | |||
}; | |||
class ParameterNumberState | |||
{ | |||
enum class Kind { rpn, nrpn }; | |||
Optional<char> newestRpnLsb, newestRpnMsb, newestNrpnLsb, newestNrpnMsb, lastSentLsb, lastSentMsb; | |||
Kind lastSentKind = Kind::rpn, newestKind = Kind::rpn; | |||
public: | |||
// If the effective parameter number has changed since the last time this function was called, | |||
// this will emit the current parameter in full (MSB and LSB). | |||
// This should be called before each data message (entry, increment, decrement: 0x06, 0x26, 0x60, 0x61) | |||
// to ensure that the data message operates on the correct parameter number. | |||
void sendIfNecessary (int channel, double time, Array<MidiMessage>& out) | |||
{ | |||
const auto newestMsb = newestKind == Kind::rpn ? newestRpnMsb : newestNrpnMsb; | |||
const auto newestLsb = newestKind == Kind::rpn ? newestRpnLsb : newestNrpnLsb; | |||
auto lastSent = std::tie (lastSentKind, lastSentMsb, lastSentLsb); | |||
const auto newest = std::tie (newestKind, newestMsb, newestLsb); | |||
if (lastSent == newest || ! newestMsb.hasValue() || ! newestLsb.hasValue()) | |||
return; | |||
out.add (MidiMessage::controllerEvent (channel, newestKind == Kind::rpn ? 0x65 : 0x63, *newestMsb).withTimeStamp (time)); | |||
out.add (MidiMessage::controllerEvent (channel, newestKind == Kind::rpn ? 0x64 : 0x62, *newestLsb).withTimeStamp (time)); | |||
lastSent = newest; | |||
} | |||
// Returns true if this is a parameter number change, and false otherwise. | |||
bool trySetProgramNumber (int controller, int value) | |||
{ | |||
switch (controller) | |||
{ | |||
case 0x65: newestRpnMsb = (char) value; newestKind = Kind::rpn; return true; | |||
case 0x64: newestRpnLsb = (char) value; newestKind = Kind::rpn; return true; | |||
case 0x63: newestNrpnMsb = (char) value; newestKind = Kind::nrpn; return true; | |||
case 0x62: newestNrpnLsb = (char) value; newestKind = Kind::nrpn; return true; | |||
} | |||
return false; | |||
} | |||
}; | |||
void MidiMessageSequence::createControllerUpdatesForTime (int channel, double time, Array<MidiMessage>& dest) | |||
{ | |||
OptionalProgramChange programChange; | |||
OptionalControllerValues controllers; | |||
OptionalPitchWheel pitchWheel; | |||
ParameterNumberState parameterNumberState; | |||
for (const auto& item : list) | |||
{ | |||
const auto& mm = item->message; | |||
if (! (mm.isForChannel (channel) && mm.getTimeStamp() <= time)) | |||
continue; | |||
if (mm.isController()) | |||
{ | |||
const auto num = mm.getControllerNumber(); | |||
if (parameterNumberState.trySetProgramNumber (num, mm.getControllerValue())) | |||
continue; | |||
if (programChange.trySetBank (num, mm.getControllerValue())) | |||
continue; | |||
constexpr int passthroughs[] { 0x06, 0x26, 0x60, 0x61 }; | |||
if (std::find (std::begin (passthroughs), std::end (passthroughs), num) != std::end (passthroughs)) | |||
{ | |||
donePitchWheel = true; | |||
dest.add (MidiMessage (mm, 0.0)); | |||
parameterNumberState.sendIfNecessary (channel, mm.getTimeStamp(), dest); | |||
dest.add (mm); | |||
} | |||
else if (mm.isController()) | |||
else | |||
{ | |||
auto controllerNumber = mm.getControllerNumber(); | |||
jassert (isPositiveAndBelow (controllerNumber, 128)); | |||
if (! doneControllers[controllerNumber]) | |||
{ | |||
doneControllers[controllerNumber] = true; | |||
dest.add (MidiMessage (mm, 0.0)); | |||
} | |||
controllers.set (num, mm.getControllerValue()); | |||
} | |||
} | |||
else if (mm.isProgramChange()) | |||
{ | |||
programChange.setProgram (mm.getProgramChangeNumber()); | |||
} | |||
else if (mm.isPitchWheel()) | |||
{ | |||
pitchWheel.set (mm.getPitchWheelValue()); | |||
} | |||
} | |||
} | |||
pitchWheel.emit (channel, dest); | |||
controllers.emit (channel, dest); | |||
// Also emits bank change messages if necessary. | |||
programChange.emit (channel, time, dest); | |||
// Set the parameter number to its final state. | |||
parameterNumberState.sendIfNecessary (channel, time, dest); | |||
} | |||
//============================================================================== | |||
//============================================================================== | |||
@@ -402,6 +528,338 @@ struct MidiMessageSequenceTest : public UnitTest | |||
expectEquals (s.getNumEvents(), 7); | |||
expectEquals (s.getIndexOfMatchingKeyUp (0), -1); // Truncated note, should be no note off | |||
expectEquals (s.getTimeOfMatchingKeyUp (1), 5.0); | |||
struct ControlValue { int control, value; }; | |||
struct DataEntry | |||
{ | |||
int controllerBase, channel, parameter, value; | |||
double time; | |||
std::array<ControlValue, 4> getControlValues() const | |||
{ | |||
return { { { controllerBase + 1, (parameter >> 7) & 0x7f }, | |||
{ controllerBase + 0, (parameter >> 0) & 0x7f }, | |||
{ 0x06, (value >> 7) & 0x7f }, | |||
{ 0x26, (value >> 0) & 0x7f } } }; | |||
} | |||
void addToSequence (MidiMessageSequence& s) const | |||
{ | |||
for (const auto& pair : getControlValues()) | |||
s.addEvent (MidiMessage::controllerEvent (channel, pair.control, pair.value), time); | |||
} | |||
bool matches (const MidiMessage* begin, const MidiMessage* end) const | |||
{ | |||
const auto isEqual = [this] (const ControlValue& cv, const MidiMessage& msg) | |||
{ | |||
return msg.getTimeStamp() == time | |||
&& msg.isController() | |||
&& msg.getChannel() == channel | |||
&& msg.getControllerNumber() == cv.control | |||
&& msg.getControllerValue() == cv.value; | |||
}; | |||
const auto pairs = getControlValues(); | |||
return std::equal (pairs.begin(), pairs.end(), begin, end, isEqual); | |||
} | |||
}; | |||
const auto addNrpn = [&] (MidiMessageSequence& seq, int channel, int parameter, int value, double time = 0.0) | |||
{ | |||
DataEntry { 0x62, channel, parameter, value, time }.addToSequence (seq); | |||
}; | |||
const auto addRpn = [&] (MidiMessageSequence& seq, int channel, int parameter, int value, double time = 0.0) | |||
{ | |||
DataEntry { 0x64, channel, parameter, value, time }.addToSequence (seq); | |||
}; | |||
const auto checkNrpn = [&] (const MidiMessage* begin, const MidiMessage* end, int channel, int parameter, int value, double time = 0.0) | |||
{ | |||
expect (DataEntry { 0x62, channel, parameter, value, time }.matches (begin, end)); | |||
}; | |||
const auto checkRpn = [&] (const MidiMessage* begin, const MidiMessage* end, int channel, int parameter, int value, double time = 0.0) | |||
{ | |||
expect (DataEntry { 0x64, channel, parameter, value, time }.matches (begin, end)); | |||
}; | |||
beginTest ("createControllerUpdatesForTime should emit (N)RPN components in the correct order"); | |||
{ | |||
const auto channel = 1; | |||
const auto number = 200; | |||
const auto value = 300; | |||
MidiMessageSequence sequence; | |||
addNrpn (sequence, channel, number, value); | |||
Array<MidiMessage> m; | |||
sequence.createControllerUpdatesForTime (channel, 1.0, m); | |||
checkNrpn (m.begin(), m.end(), channel, number, value); | |||
} | |||
beginTest ("createControllerUpdatesForTime ignores (N)RPNs after the final requested time"); | |||
{ | |||
const auto channel = 2; | |||
const auto number = 123; | |||
const auto value = 456; | |||
MidiMessageSequence sequence; | |||
addRpn (sequence, channel, number, value, 0.5); | |||
addRpn (sequence, channel, 111, 222, 1.5); | |||
addRpn (sequence, channel, 333, 444, 2.5); | |||
Array<MidiMessage> m; | |||
sequence.createControllerUpdatesForTime (channel, 1.0, m); | |||
checkRpn (m.begin(), std::next (m.begin(), 4), channel, number, value, 0.5); | |||
} | |||
beginTest ("createControllerUpdatesForTime should emit separate (N)RPN messages when appropriate"); | |||
{ | |||
const auto channel = 2; | |||
const auto numberA = 1111; | |||
const auto valueA = 9999; | |||
const auto numberB = 8888; | |||
const auto valueB = 2222; | |||
const auto numberC = 7777; | |||
const auto valueC = 3333; | |||
const auto numberD = 6666; | |||
const auto valueD = 4444; | |||
const auto time = 0.5; | |||
MidiMessageSequence sequence; | |||
addRpn (sequence, channel, numberA, valueA, time); | |||
addRpn (sequence, channel, numberB, valueB, time); | |||
addNrpn (sequence, channel, numberC, valueC, time); | |||
addNrpn (sequence, channel, numberD, valueD, time); | |||
Array<MidiMessage> m; | |||
sequence.createControllerUpdatesForTime (channel, time * 2, m); | |||
checkRpn (std::next (m.begin(), 0), std::next (m.begin(), 4), channel, numberA, valueA, time); | |||
checkRpn (std::next (m.begin(), 4), std::next (m.begin(), 8), channel, numberB, valueB, time); | |||
checkNrpn (std::next (m.begin(), 8), std::next (m.begin(), 12), channel, numberC, valueC, time); | |||
checkNrpn (std::next (m.begin(), 12), std::next (m.begin(), 16), channel, numberD, valueD, time); | |||
} | |||
beginTest ("createControllerUpdatesForTime correctly emits (N)RPN messages on multiple channels"); | |||
{ | |||
struct Info { int channel, number, value; }; | |||
const Info infos[] { { 2, 1111, 9999 }, | |||
{ 8, 8888, 2222 }, | |||
{ 5, 7777, 3333 }, | |||
{ 1, 6666, 4444 } }; | |||
const auto time = 0.5; | |||
MidiMessageSequence sequence; | |||
for (const auto& info : infos) | |||
addRpn (sequence, info.channel, info.number, info.value, time); | |||
for (const auto& info : infos) | |||
{ | |||
Array<MidiMessage> m; | |||
sequence.createControllerUpdatesForTime (info.channel, time * 2, m); | |||
checkRpn (std::next (m.begin(), 0), std::next (m.begin(), 4), info.channel, info.number, info.value, time); | |||
} | |||
} | |||
const auto messagesAreEqual = [] (const MidiMessage& a, const MidiMessage& b) | |||
{ | |||
return std::equal (a.getRawData(), a.getRawData() + a.getRawDataSize(), | |||
b.getRawData(), b.getRawData() + b.getRawDataSize()); | |||
}; | |||
beginTest ("createControllerUpdatesForTime sends bank select messages when the next program is in a new bank"); | |||
{ | |||
MidiMessageSequence sequence; | |||
const auto time = 0.0; | |||
const auto channel = 1; | |||
sequence.addEvent (MidiMessage::programChange (channel, 5), time); | |||
sequence.addEvent (MidiMessage::controllerEvent (channel, 0x00, 128), time); | |||
sequence.addEvent (MidiMessage::controllerEvent (channel, 0x20, 64), time); | |||
sequence.addEvent (MidiMessage::programChange (channel, 63), time); | |||
const Array<MidiMessage> finalEvents { MidiMessage::controllerEvent (channel, 0x00, 50), | |||
MidiMessage::controllerEvent (channel, 0x20, 40), | |||
MidiMessage::programChange (channel, 30) }; | |||
for (const auto& e : finalEvents) | |||
sequence.addEvent (e); | |||
Array<MidiMessage> m; | |||
sequence.createControllerUpdatesForTime (channel, 1.0, m); | |||
expect (std::equal (m.begin(), m.end(), finalEvents.begin(), finalEvents.end(), messagesAreEqual)); | |||
} | |||
beginTest ("createControllerUpdatesForTime preserves all Data Increment and Data Decrement messages"); | |||
{ | |||
MidiMessageSequence sequence; | |||
const auto time = 0.0; | |||
const auto channel = 1; | |||
const Array<MidiMessage> messages { MidiMessage::controllerEvent (channel, 0x60, 0), | |||
MidiMessage::controllerEvent (channel, 0x06, 100), | |||
MidiMessage::controllerEvent (channel, 0x26, 50), | |||
MidiMessage::controllerEvent (channel, 0x60, 10), | |||
MidiMessage::controllerEvent (channel, 0x61, 10), | |||
MidiMessage::controllerEvent (channel, 0x06, 20), | |||
MidiMessage::controllerEvent (channel, 0x26, 30), | |||
MidiMessage::controllerEvent (channel, 0x61, 10), | |||
MidiMessage::controllerEvent (channel, 0x61, 20) }; | |||
for (const auto& m : messages) | |||
sequence.addEvent (m, time); | |||
Array<MidiMessage> m; | |||
sequence.createControllerUpdatesForTime (channel, 1.0, m); | |||
expect (std::equal (m.begin(), m.end(), messages.begin(), messages.end(), messagesAreEqual)); | |||
} | |||
beginTest ("createControllerUpdatesForTime does not emit redundant parameter number changes"); | |||
{ | |||
MidiMessageSequence sequence; | |||
const auto time = 0.0; | |||
const auto channel = 1; | |||
const Array<MidiMessage> messages { MidiMessage::controllerEvent (channel, 0x65, 0), | |||
MidiMessage::controllerEvent (channel, 0x64, 100), | |||
MidiMessage::controllerEvent (channel, 0x63, 50), | |||
MidiMessage::controllerEvent (channel, 0x62, 10), | |||
MidiMessage::controllerEvent (channel, 0x06, 10) }; | |||
for (const auto& m : messages) | |||
sequence.addEvent (m, time); | |||
Array<MidiMessage> m; | |||
sequence.createControllerUpdatesForTime (channel, 1.0, m); | |||
const Array<MidiMessage> expected { MidiMessage::controllerEvent (channel, 0x63, 50), | |||
MidiMessage::controllerEvent (channel, 0x62, 10), | |||
MidiMessage::controllerEvent (channel, 0x06, 10) }; | |||
expect (std::equal (m.begin(), m.end(), expected.begin(), expected.end(), messagesAreEqual)); | |||
} | |||
beginTest ("createControllerUpdatesForTime sets parameter number correctly at end of sequence"); | |||
{ | |||
MidiMessageSequence sequence; | |||
const auto time = 0.0; | |||
const auto channel = 1; | |||
const Array<MidiMessage> messages { MidiMessage::controllerEvent (channel, 0x65, 0), | |||
MidiMessage::controllerEvent (channel, 0x64, 100), | |||
MidiMessage::controllerEvent (channel, 0x63, 50), | |||
MidiMessage::controllerEvent (channel, 0x62, 10), | |||
MidiMessage::controllerEvent (channel, 0x06, 10), | |||
MidiMessage::controllerEvent (channel, 0x64, 5) }; | |||
for (const auto& m : messages) | |||
sequence.addEvent (m, time); | |||
const auto finalTime = 1.0; | |||
Array<MidiMessage> m; | |||
sequence.createControllerUpdatesForTime (channel, finalTime, m); | |||
const Array<MidiMessage> expected { MidiMessage::controllerEvent (channel, 0x63, 50), | |||
MidiMessage::controllerEvent (channel, 0x62, 10), | |||
MidiMessage::controllerEvent (channel, 0x06, 10), | |||
// Note: we should send both the MSB and LSB! | |||
MidiMessage::controllerEvent (channel, 0x65, 0).withTimeStamp (finalTime), | |||
MidiMessage::controllerEvent (channel, 0x64, 5).withTimeStamp (finalTime) }; | |||
expect (std::equal (m.begin(), m.end(), expected.begin(), expected.end(), messagesAreEqual)); | |||
} | |||
beginTest ("createControllerUpdatesForTime does not emit duplicate parameter number change messages"); | |||
{ | |||
MidiMessageSequence sequence; | |||
const auto time = 0.0; | |||
const auto channel = 1; | |||
const Array<MidiMessage> messages { MidiMessage::controllerEvent (channel, 0x65, 1), | |||
MidiMessage::controllerEvent (channel, 0x64, 2), | |||
MidiMessage::controllerEvent (channel, 0x63, 3), | |||
MidiMessage::controllerEvent (channel, 0x62, 4), | |||
MidiMessage::controllerEvent (channel, 0x06, 10), | |||
MidiMessage::controllerEvent (channel, 0x63, 30), | |||
MidiMessage::controllerEvent (channel, 0x62, 40), | |||
MidiMessage::controllerEvent (channel, 0x63, 3), | |||
MidiMessage::controllerEvent (channel, 0x62, 4), | |||
MidiMessage::controllerEvent (channel, 0x60, 5), | |||
MidiMessage::controllerEvent (channel, 0x65, 10) }; | |||
for (const auto& m : messages) | |||
sequence.addEvent (m, time); | |||
const auto finalTime = 1.0; | |||
Array<MidiMessage> m; | |||
sequence.createControllerUpdatesForTime (channel, finalTime, m); | |||
const Array<MidiMessage> expected { MidiMessage::controllerEvent (channel, 0x63, 3), | |||
MidiMessage::controllerEvent (channel, 0x62, 4), | |||
MidiMessage::controllerEvent (channel, 0x06, 10), | |||
// Parameter number is set to (30, 40) then back to (3, 4), | |||
// so there is no need to resend it | |||
MidiMessage::controllerEvent (channel, 0x60, 5), | |||
// Set parameter number to final value | |||
MidiMessage::controllerEvent (channel, 0x65, 10).withTimeStamp (finalTime), | |||
MidiMessage::controllerEvent (channel, 0x64, 2) .withTimeStamp (finalTime) }; | |||
expect (std::equal (m.begin(), m.end(), expected.begin(), expected.end(), messagesAreEqual)); | |||
} | |||
beginTest ("createControllerUpdatesForTime emits bank change messages immediately before program change"); | |||
{ | |||
MidiMessageSequence sequence; | |||
const auto time = 0.0; | |||
const auto channel = 1; | |||
const Array<MidiMessage> messages { MidiMessage::controllerEvent (channel, 0x00, 1), | |||
MidiMessage::controllerEvent (channel, 0x20, 2), | |||
MidiMessage::controllerEvent (channel, 0x65, 0), | |||
MidiMessage::controllerEvent (channel, 0x64, 0), | |||
MidiMessage::programChange (channel, 5) }; | |||
for (const auto& m : messages) | |||
sequence.addEvent (m, time); | |||
const auto finalTime = 1.0; | |||
Array<MidiMessage> m; | |||
sequence.createControllerUpdatesForTime (channel, finalTime, m); | |||
const Array<MidiMessage> expected { MidiMessage::controllerEvent (channel, 0x00, 1), | |||
MidiMessage::controllerEvent (channel, 0x20, 2), | |||
MidiMessage::programChange (channel, 5), | |||
MidiMessage::controllerEvent (channel, 0x65, 0).withTimeStamp (finalTime), | |||
MidiMessage::controllerEvent (channel, 0x64, 0).withTimeStamp (finalTime) }; | |||
expect (std::equal (m.begin(), m.end(), expected.begin(), expected.end(), messagesAreEqual)); | |||
} | |||
} | |||
}; | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -53,9 +53,6 @@ public: | |||
/** Move assignment operator */ | |||
MidiMessageSequence& operator= (MidiMessageSequence&&) noexcept; | |||
/** Destructor. */ | |||
~MidiMessageSequence(); | |||
//============================================================================== | |||
/** Structure used to hold midi events in the sequence. | |||
@@ -68,9 +65,6 @@ public: | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Destructor. */ | |||
~MidiEventHolder(); | |||
/** The message itself, whose timestamp is used to specify the event's time. */ | |||
MidiMessage message; | |||
@@ -277,6 +271,21 @@ public: | |||
As well as controllers, it will also recreate the midi program number | |||
and pitch bend position. | |||
This function has special handling for the "bank select" and "data entry" | |||
controllers (0x00, 0x20, 0x06, 0x26, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65). | |||
If the sequence contains multiple bank select and program change messages, | |||
only the bank select messages immediately preceding the final program change | |||
message will be kept. | |||
All "data increment" and "data decrement" messages will be retained. Some hardware will | |||
ignore the requested increment/decrement values, so retaining all messages is the only | |||
way to ensure compatibility with all hardware. | |||
"Parameter number" changes will be slightly condensed. Only the parameter number | |||
events immediately preceding each data entry event will be kept. The parameter number | |||
will also be set to its final value at the end of the sequence, if necessary. | |||
@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 | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -55,11 +55,6 @@ void MidiRPNDetector::reset() noexcept | |||
} | |||
//============================================================================== | |||
MidiRPNDetector::ChannelState::ChannelState() noexcept | |||
: parameterMSB (0xff), parameterLSB (0xff), valueMSB (0xff), valueLSB (0xff), isNRPN (false) | |||
{ | |||
} | |||
bool MidiRPNDetector::ChannelState::handleController (int channel, | |||
int controllerNumber, | |||
int value, | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -95,14 +95,13 @@ 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; | |||
uint8 parameterMSB = 0xff, parameterLSB = 0xff, valueMSB = 0xff, valueLSB = 0xff; | |||
bool isNRPN = false; | |||
}; | |||
//============================================================================== | |||
@@ -0,0 +1,47 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
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. | |||
============================================================================== | |||
*/ | |||
#include "../juce_MidiDataConcatenator.h" | |||
#include "juce_UMPProtocols.h" | |||
#include "juce_UMPUtils.h" | |||
#include "juce_UMPacket.h" | |||
#include "juce_UMPSysEx7.h" | |||
#include "juce_UMPView.h" | |||
#include "juce_UMPIterator.h" | |||
#include "juce_UMPackets.h" | |||
#include "juce_UMPFactory.h" | |||
#include "juce_UMPConversion.h" | |||
#include "juce_UMPMidi1ToBytestreamTranslator.h" | |||
#include "juce_UMPMidi1ToMidi2DefaultTranslator.h" | |||
#include "juce_UMPConverters.h" | |||
#include "juce_UMPDispatcher.h" | |||
#include "juce_UMPReceiver.h" | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace ump = universal_midi_packets; | |||
} | |||
#endif |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -20,6 +20,8 @@ | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
@@ -324,3 +326,5 @@ struct Conversion | |||
} | |||
} | |||
#endif |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -20,6 +20,8 @@ | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
@@ -163,3 +165,5 @@ namespace universal_midi_packets | |||
}; | |||
} | |||
} | |||
#endif |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -20,6 +20,8 @@ | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
@@ -108,7 +110,13 @@ public: | |||
{ | |||
using CallbackPtr = decltype (std::addressof (callback)); | |||
struct Callback | |||
#if JUCE_MINGW | |||
#define JUCE_MINGW_HIDDEN_VISIBILITY __attribute__ ((visibility ("hidden"))) | |||
#else | |||
#define JUCE_MINGW_HIDDEN_VISIBILITY | |||
#endif | |||
struct JUCE_MINGW_HIDDEN_VISIBILITY Callback | |||
{ | |||
Callback (BytestreamToUMPDispatcher& d, CallbackPtr c) | |||
: dispatch (d), callbackPtr (c) {} | |||
@@ -127,6 +135,8 @@ public: | |||
CallbackPtr callbackPtr = nullptr; | |||
}; | |||
#undef JUCE_MINGW_HIDDEN_VISIBILITY | |||
Callback inputCallback { *this, &callback }; | |||
concatenator.pushMidiData (begin, int (end - begin), timestamp, (void*) nullptr, inputCallback); | |||
} | |||
@@ -188,3 +198,5 @@ private: | |||
} | |||
} | |||
#endif |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -20,6 +20,8 @@ | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
@@ -532,3 +534,5 @@ struct Factory | |||
} | |||
} | |||
#endif |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -20,6 +20,8 @@ | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
@@ -54,7 +56,7 @@ public: | |||
using value_type = View; | |||
using reference = const View&; | |||
using pointer = const View*; | |||
using iterator_category = std::input_iterator_tag; | |||
using iterator_category = std::forward_iterator_tag; | |||
/** Moves this iterator to the next packet in the range. */ | |||
Iterator& operator++() noexcept | |||
@@ -124,3 +126,5 @@ private: | |||
} | |||
} | |||
#endif |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -20,6 +20,8 @@ | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
@@ -211,3 +213,5 @@ private: | |||
} | |||
} | |||
#endif |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -20,6 +20,8 @@ | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
@@ -185,3 +187,5 @@ private: | |||
} | |||
} | |||
#endif |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -20,6 +20,8 @@ | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
@@ -42,3 +44,5 @@ enum class MidiProtocol | |||
} | |||
} | |||
#endif |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -20,6 +20,8 @@ | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
@@ -27,6 +29,8 @@ namespace universal_midi_packets | |||
/** | |||
A base class for classes which receive Universal MIDI Packets from an input. | |||
@tags{Audio} | |||
*/ | |||
struct Receiver | |||
{ | |||
@@ -38,3 +42,5 @@ struct Receiver | |||
} | |||
} | |||
#endif |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -39,12 +39,12 @@ SysEx7::PacketBytes SysEx7::getDataBytes (const PacketX2& packet) | |||
return | |||
{ | |||
{ packet.getU8<2>(), | |||
packet.getU8<3>(), | |||
packet.getU8<4>(), | |||
packet.getU8<5>(), | |||
packet.getU8<6>(), | |||
packet.getU8<7>() }, | |||
{ { packet.getU8<2>(), | |||
packet.getU8<3>(), | |||
packet.getU8<4>(), | |||
packet.getU8<5>(), | |||
packet.getU8<6>(), | |||
packet.getU8<7>() } }, | |||
jmin (numBytes, maxBytes) | |||
}; | |||
} |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -20,13 +20,15 @@ | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
This struct acts as a single-file namespace for Univeral MIDI Packet | |||
This struct acts as a single-file namespace for Universal MIDI Packet | |||
functionality related to 7-bit SysEx. | |||
@tags{Audio} | |||
@@ -71,3 +73,5 @@ struct SysEx7 | |||
} | |||
} | |||
#endif |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -20,6 +20,8 @@ | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
@@ -111,3 +113,5 @@ struct Utils | |||
} | |||
} | |||
#endif |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -20,6 +20,8 @@ | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
@@ -86,3 +88,5 @@ private: | |||
} | |||
} | |||
#endif |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -25,8 +25,6 @@ namespace juce | |||
namespace universal_midi_packets | |||
{ | |||
#if JUCE_UNIT_TESTS | |||
constexpr uint8_t operator""_u8 (unsigned long long int i) { return static_cast<uint8_t> (i); } | |||
constexpr uint16_t operator""_u16 (unsigned long long int i) { return static_cast<uint16_t> (i); } | |||
constexpr uint32_t operator""_u32 (unsigned long long int i) { return static_cast<uint32_t> (i); } | |||
@@ -697,7 +695,7 @@ public: | |||
for (const auto typecode : typecodesX1) | |||
{ | |||
Packets p; | |||
p.add (PacketX1 { (uint32_t) (typecode << 0x1c | (random.nextInt64() & 0xffffff)) }); | |||
p.add (PacketX1 { (uint32_t) ((int64_t) typecode << 0x1c | (random.nextInt64() & 0xffffff)) }); | |||
checkMidi2ToMidi1Conversion (p, p); | |||
} | |||
@@ -966,8 +964,10 @@ private: | |||
template <typename Fn> | |||
void forEachNonSysExTestMessage (Random& random, Fn&& fn) | |||
{ | |||
for (uint8_t firstByte = 0x80; firstByte != 0x00; ++firstByte) | |||
for (uint16_t counter = 0x80; counter != 0x100; ++counter) | |||
{ | |||
const auto firstByte = (uint8_t) counter; | |||
if (firstByte == 0xf0 || firstByte == 0xf7) | |||
continue; // sysEx is tested separately | |||
@@ -990,9 +990,9 @@ private: | |||
} | |||
} | |||
#if JUCE_WINDOWS | |||
#if JUCE_WINDOWS && ! JUCE_MINGW | |||
#define JUCE_CHECKED_ITERATOR(msg, size) \ | |||
stdext::checked_array_iterator<typename std::remove_reference<decltype (msg)>::type> ((msg), (size)) | |||
stdext::checked_array_iterator<typename std::remove_reference<decltype (msg)>::type> ((msg), (size_t) (size)) | |||
#else | |||
#define JUCE_CHECKED_ITERATOR(msg, size) (msg) | |||
#endif | |||
@@ -1014,7 +1014,5 @@ private: | |||
static UniversalMidiPacketTests universalMidiPacketTests; | |||
#endif | |||
} | |||
} |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -20,6 +20,8 @@ | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
@@ -27,6 +29,8 @@ namespace universal_midi_packets | |||
/** | |||
Holds a single Universal MIDI Packet. | |||
@tags{Audio} | |||
*/ | |||
template <size_t numWords> | |||
class Packet | |||
@@ -185,3 +189,5 @@ using PacketX4 = Packet<4>; | |||
} | |||
} | |||
#endif |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -20,6 +20,8 @@ | |||
============================================================================== | |||
*/ | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
@@ -90,3 +92,5 @@ private: | |||
} | |||
} | |||
#endif |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -43,16 +43,20 @@ MPEInstrument::MPEInstrument() noexcept | |||
mpeInstrumentFill (isMemberChannelSustained, false); | |||
pitchbendDimension.value = &MPENote::pitchbend; | |||
pressureDimension.value = &MPENote::pressure; | |||
timbreDimension.value = &MPENote::timbre; | |||
pressureDimension.value = &MPENote::pressure; | |||
timbreDimension.value = &MPENote::timbre; | |||
resetLastReceivedValues(); | |||
legacyMode.isEnabled = false; | |||
legacyMode.pitchbendRange = 2; | |||
legacyMode.channelRange = allChannels; | |||
} | |||
MPEInstrument::MPEInstrument (MPEZoneLayout layout) | |||
: MPEInstrument() | |||
{ | |||
setZoneLayout (layout); | |||
} | |||
MPEInstrument::~MPEInstrument() = default; | |||
//============================================================================== | |||
@@ -84,21 +88,30 @@ void MPEInstrument::setZoneLayout (MPEZoneLayout newLayout) | |||
const ScopedLock sl (lock); | |||
legacyMode.isEnabled = false; | |||
zoneLayout = newLayout; | |||
resetLastReceivedValues(); | |||
if (zoneLayout != newLayout) | |||
{ | |||
zoneLayout = newLayout; | |||
listeners.call ([=] (Listener& l) { l.zoneLayoutChanged(); }); | |||
} | |||
} | |||
//============================================================================== | |||
void MPEInstrument::enableLegacyMode (int pitchbendRange, Range<int> channelRange) | |||
{ | |||
if (legacyMode.isEnabled) | |||
return; | |||
releaseAllNotes(); | |||
const ScopedLock sl (lock); | |||
legacyMode.isEnabled = true; | |||
legacyMode.pitchbendRange = pitchbendRange; | |||
legacyMode.channelRange = channelRange; | |||
zoneLayout.clearAllZones(); | |||
listeners.call ([=] (Listener& l) { l.zoneLayoutChanged(); }); | |||
} | |||
bool MPEInstrument::isLegacyModeEnabled() const noexcept | |||
@@ -117,7 +130,12 @@ void MPEInstrument::setLegacyModeChannelRange (Range<int> channelRange) | |||
releaseAllNotes(); | |||
const ScopedLock sl (lock); | |||
legacyMode.channelRange = channelRange; | |||
if (legacyMode.channelRange != channelRange) | |||
{ | |||
legacyMode.channelRange = channelRange; | |||
listeners.call ([=] (Listener& l) { l.zoneLayoutChanged(); }); | |||
} | |||
} | |||
int MPEInstrument::getLegacyModePitchbendRange() const noexcept | |||
@@ -131,7 +149,12 @@ void MPEInstrument::setLegacyModePitchbendRange (int pitchbendRange) | |||
releaseAllNotes(); | |||
const ScopedLock sl (lock); | |||
legacyMode.pitchbendRange = pitchbendRange; | |||
if (legacyMode.pitchbendRange != pitchbendRange) | |||
{ | |||
legacyMode.pitchbendRange = pitchbendRange; | |||
listeners.call ([=] (Listener& l) { l.zoneLayoutChanged(); }); | |||
} | |||
} | |||
//============================================================================== | |||
@@ -242,7 +265,7 @@ void MPEInstrument::processMidiResetAllControllersMessage (const MidiMessage& me | |||
if (legacyMode.isEnabled && legacyMode.channelRange.contains (message.getChannel())) | |||
{ | |||
for (auto i = notes.size(); --i >= 0;) | |||
for (int i = notes.size(); --i >= 0;) | |||
{ | |||
auto& note = notes.getReference (i); | |||
@@ -260,7 +283,7 @@ void MPEInstrument::processMidiResetAllControllersMessage (const MidiMessage& me | |||
auto zone = (message.getChannel() == 1 ? zoneLayout.getLowerZone() | |||
: zoneLayout.getUpperZone()); | |||
for (auto i = notes.size(); --i >= 0;) | |||
for (int i = notes.size(); --i >= 0;) | |||
{ | |||
auto& note = notes.getReference (i); | |||
@@ -348,11 +371,11 @@ void MPEInstrument::noteOff (int midiChannel, | |||
int midiNoteNumber, | |||
MPEValue midiNoteOffVelocity) | |||
{ | |||
const ScopedLock sl (lock); | |||
if (notes.isEmpty() || ! isUsingChannel (midiChannel)) | |||
return; | |||
const ScopedLock sl (lock); | |||
if (auto* note = getNotePtr (midiChannel, midiNoteNumber)) | |||
{ | |||
note->keyState = (note->keyState == MPENote::keyDownAndSustained) ? MPENote::sustained : MPENote::off; | |||
@@ -401,7 +424,7 @@ void MPEInstrument::polyAftertouch (int midiChannel, int midiNoteNumber, MPEValu | |||
{ | |||
const ScopedLock sl (lock); | |||
for (auto i = notes.size(); --i >= 0;) | |||
for (int i = notes.size(); --i >= 0;) | |||
{ | |||
auto& note = notes.getReference (i); | |||
@@ -435,7 +458,7 @@ void MPEInstrument::updateDimension (int midiChannel, MPEDimension& dimension, M | |||
{ | |||
if (dimension.trackingMode == allNotesOnChannel) | |||
{ | |||
for (auto i = notes.size(); --i >= 0;) | |||
for (int i = notes.size(); --i >= 0;) | |||
{ | |||
auto& note = notes.getReference (i); | |||
@@ -464,7 +487,7 @@ void MPEInstrument::updateDimensionMaster (bool isLowerZone, MPEDimension& dimen | |||
if (! zone.isActive()) | |||
return; | |||
for (auto i = notes.size(); --i >= 0;) | |||
for (int i = notes.size(); --i >= 0;) | |||
{ | |||
auto& note = notes.getReference (i); | |||
@@ -573,7 +596,7 @@ void MPEInstrument::handleSustainOrSostenuto (int midiChannel, bool isDown, bool | |||
auto zone = (midiChannel == 1 ? zoneLayout.getLowerZone() | |||
: zoneLayout.getUpperZone()); | |||
for (auto i = notes.size(); --i >= 0;) | |||
for (int i = notes.size(); --i >= 0;) | |||
{ | |||
auto& note = notes.getReference (i); | |||
@@ -605,11 +628,15 @@ void MPEInstrument::handleSustainOrSostenuto (int midiChannel, bool isDown, bool | |||
if (! legacyMode.isEnabled) | |||
{ | |||
if (zone.isLowerZone()) | |||
for (auto i = zone.getFirstMemberChannel(); i <= zone.getLastMemberChannel(); ++i) | |||
{ | |||
for (int i = zone.getFirstMemberChannel(); i <= zone.getLastMemberChannel(); ++i) | |||
isMemberChannelSustained[i - 1] = isDown; | |||
} | |||
else | |||
for (auto i = zone.getFirstMemberChannel(); i >= zone.getLastMemberChannel(); --i) | |||
{ | |||
for (int i = zone.getFirstMemberChannel(); i >= zone.getLastMemberChannel(); --i) | |||
isMemberChannelSustained[i - 1] = isDown; | |||
} | |||
} | |||
} | |||
} | |||
@@ -664,6 +691,17 @@ MPENote MPEInstrument::getNote (int index) const noexcept | |||
return notes[index]; | |||
} | |||
MPENote MPEInstrument::getNoteWithID (uint16 noteID) const noexcept | |||
{ | |||
const ScopedLock sl (lock); | |||
for (auto& note : notes) | |||
if (note.noteID == noteID) | |||
return note; | |||
return {}; | |||
} | |||
//============================================================================== | |||
MPENote MPEInstrument::getMostRecentNote (int midiChannel) const noexcept | |||
{ | |||
@@ -727,6 +765,8 @@ MPENote* MPEInstrument::getNotePtr (int midiChannel, TrackingMode mode) noexcept | |||
//============================================================================== | |||
const MPENote* MPEInstrument::getLastNotePlayedPtr (int midiChannel) const noexcept | |||
{ | |||
const ScopedLock sl (lock); | |||
for (auto i = notes.size(); --i >= 0;) | |||
{ | |||
auto& note = notes.getReference (i); | |||
@@ -833,6 +873,7 @@ public: | |||
testLayout.setUpperZone (6); | |||
} | |||
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6262) | |||
void runTest() override | |||
{ | |||
beginTest ("initial zone layout"); | |||
@@ -2145,6 +2186,7 @@ public: | |||
} | |||
} | |||
} | |||
JUCE_END_IGNORE_WARNINGS_MSVC | |||
private: | |||
//============================================================================== | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -38,10 +38,8 @@ namespace juce | |||
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. | |||
The class has a Listener class that can be used to react to note and | |||
state changes and trigger some functionality for your application. | |||
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, | |||
@@ -59,11 +57,14 @@ public: | |||
This will construct an MPE instrument with inactive lower and upper zones. | |||
In order to process incoming MIDI, call setZoneLayout, define the layout | |||
via MIDI RPN messages, or set the instrument to legacy mode. | |||
In order to process incoming MIDI messages call setZoneLayout, use the MPEZoneLayout | |||
constructor, define the layout via MIDI RPN messages, or set the instrument to legacy mode. | |||
*/ | |||
MPEInstrument() noexcept; | |||
/** Constructs an MPE instrument with the specified zone layout. */ | |||
MPEInstrument (MPEZoneLayout layout); | |||
/** Destructor. */ | |||
virtual ~MPEInstrument(); | |||
@@ -229,6 +230,9 @@ public: | |||
*/ | |||
MPENote getNote (int midiChannel, int midiNoteNumber) const noexcept; | |||
/** Returns the note with a given ID. */ | |||
MPENote getNoteWithID (uint16 noteID) 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 | |||
@@ -244,8 +248,8 @@ public: | |||
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. | |||
/** Derive from this class to be informed about any changes in the MPE notes played | |||
by this instrument, and any changes to its zone layout. | |||
Note: This listener type receives its callbacks immediately, and not | |||
via the message thread (so you might be for example in the MIDI thread). | |||
@@ -297,6 +301,11 @@ public: | |||
and should therefore stop playing. | |||
*/ | |||
virtual void noteReleased (MPENote finishedNote) { ignoreUnused (finishedNote); } | |||
/** Implement this callback to be informed whenever the MPE zone layout | |||
or legacy mode settings of this instrument have been changed. | |||
*/ | |||
virtual void zoneLayoutChanged() {} | |||
}; | |||
//============================================================================== | |||
@@ -307,7 +316,9 @@ public: | |||
void removeListener (Listener* listenerToRemove); | |||
//============================================================================== | |||
/** Puts the instrument into legacy mode. | |||
/** Puts the instrument into legacy mode. If legacy mode is already enabled this method | |||
does nothing. | |||
As a side effect, this will discard all currently playing notes, | |||
and call noteReleased for all of them. | |||
@@ -360,9 +371,9 @@ private: | |||
struct LegacyMode | |||
{ | |||
bool isEnabled; | |||
bool isEnabled = false; | |||
Range<int> channelRange; | |||
int pitchbendRange; | |||
int pitchbendRange = 2; | |||
}; | |||
struct MPEDimension | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -115,7 +115,7 @@ struct JUCE_API MPENote | |||
*/ | |||
MPEValue noteOnVelocity { MPEValue::minValue() }; | |||
/** Current per-note pitchbend of the note (in units of MIDI pitchwheel | |||
/** 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, | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -25,12 +25,10 @@ namespace juce | |||
MPESynthesiser::MPESynthesiser() | |||
{ | |||
MPEZoneLayout zoneLayout; | |||
zoneLayout.setLowerZone (15); | |||
setZoneLayout (zoneLayout); | |||
} | |||
MPESynthesiser::MPESynthesiser (MPEInstrument* mpeInstrument) : MPESynthesiserBase (mpeInstrument) | |||
MPESynthesiser::MPESynthesiser (MPEInstrument& mpeInstrument) | |||
: MPESynthesiserBase (mpeInstrument) | |||
{ | |||
} | |||
@@ -129,7 +127,7 @@ void MPESynthesiser::noteReleased (MPENote finishedNote) | |||
{ | |||
auto* voice = voices.getUnchecked (i); | |||
if (voice->isCurrentlyPlayingNote(finishedNote)) | |||
if (voice->isCurrentlyPlayingNote (finishedNote)) | |||
stopVoice (voice, finishedNote, true); | |||
} | |||
} | |||
@@ -305,11 +303,16 @@ 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 (auto* voice : voices) | |||
{ | |||
voice->currentlyPlayingNote.noteOffVelocity = MPEValue::from7BitInt (64); // some reasonable number | |||
voice->currentlyPlayingNote.keyState = MPENote::off; | |||
voice->noteStopped (allowTailOff); | |||
} | |||
} | |||
// finally make sure the MPE Instrument also doesn't have any notes anymore. | |||
instrument->releaseAllNotes(); | |||
instrument.releaseAllNotes(); | |||
} | |||
//============================================================================== | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -65,11 +65,10 @@ public: | |||
/** 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* instrumentToUse); | |||
MPESynthesiser (MPEInstrument& instrumentToUse); | |||
/** Destructor. */ | |||
~MPESynthesiser() override; | |||
@@ -303,7 +302,7 @@ protected: | |||
private: | |||
//============================================================================== | |||
bool shouldStealVoices = false; | |||
std::atomic<bool> shouldStealVoices { false }; | |||
uint32 lastNoteOnCounter = 0; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiser) | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -24,80 +24,79 @@ namespace juce | |||
{ | |||
MPESynthesiserBase::MPESynthesiserBase() | |||
: instrument (new MPEInstrument) | |||
: instrument (defaultInstrument) | |||
{ | |||
instrument->addListener (this); | |||
instrument.addListener (this); | |||
} | |||
MPESynthesiserBase::MPESynthesiserBase (MPEInstrument* inst) | |||
MPESynthesiserBase::MPESynthesiserBase (MPEInstrument& inst) | |||
: instrument (inst) | |||
{ | |||
jassert (instrument != nullptr); | |||
instrument->addListener (this); | |||
instrument.addListener (this); | |||
} | |||
//============================================================================== | |||
MPEZoneLayout MPESynthesiserBase::getZoneLayout() const noexcept | |||
{ | |||
return instrument->getZoneLayout(); | |||
return instrument.getZoneLayout(); | |||
} | |||
void MPESynthesiserBase::setZoneLayout (MPEZoneLayout newLayout) | |||
{ | |||
instrument->setZoneLayout (newLayout); | |||
instrument.setZoneLayout (newLayout); | |||
} | |||
//============================================================================== | |||
void MPESynthesiserBase::enableLegacyMode (int pitchbendRange, Range<int> channelRange) | |||
{ | |||
instrument->enableLegacyMode (pitchbendRange, channelRange); | |||
instrument.enableLegacyMode (pitchbendRange, channelRange); | |||
} | |||
bool MPESynthesiserBase::isLegacyModeEnabled() const noexcept | |||
{ | |||
return instrument->isLegacyModeEnabled(); | |||
return instrument.isLegacyModeEnabled(); | |||
} | |||
Range<int> MPESynthesiserBase::getLegacyModeChannelRange() const noexcept | |||
{ | |||
return instrument->getLegacyModeChannelRange(); | |||
return instrument.getLegacyModeChannelRange(); | |||
} | |||
void MPESynthesiserBase::setLegacyModeChannelRange (Range<int> channelRange) | |||
{ | |||
instrument->setLegacyModeChannelRange (channelRange); | |||
instrument.setLegacyModeChannelRange (channelRange); | |||
} | |||
int MPESynthesiserBase::getLegacyModePitchbendRange() const noexcept | |||
{ | |||
return instrument->getLegacyModePitchbendRange(); | |||
return instrument.getLegacyModePitchbendRange(); | |||
} | |||
void MPESynthesiserBase::setLegacyModePitchbendRange (int pitchbendRange) | |||
{ | |||
instrument->setLegacyModePitchbendRange (pitchbendRange); | |||
instrument.setLegacyModePitchbendRange (pitchbendRange); | |||
} | |||
//============================================================================== | |||
void MPESynthesiserBase::setPressureTrackingMode (TrackingMode modeToUse) | |||
{ | |||
instrument->setPressureTrackingMode (modeToUse); | |||
instrument.setPressureTrackingMode (modeToUse); | |||
} | |||
void MPESynthesiserBase::setPitchbendTrackingMode (TrackingMode modeToUse) | |||
{ | |||
instrument->setPitchbendTrackingMode (modeToUse); | |||
instrument.setPitchbendTrackingMode (modeToUse); | |||
} | |||
void MPESynthesiserBase::setTimbreTrackingMode (TrackingMode modeToUse) | |||
{ | |||
instrument->setTimbreTrackingMode (modeToUse); | |||
instrument.setTimbreTrackingMode (modeToUse); | |||
} | |||
//============================================================================== | |||
void MPESynthesiserBase::handleMidiEvent (const MidiMessage& m) | |||
{ | |||
instrument->processNextMidiEvent (m); | |||
instrument.processNextMidiEvent (m); | |||
} | |||
//============================================================================== | |||
@@ -148,7 +147,7 @@ void MPESynthesiserBase::setCurrentPlaybackSampleRate (const double newRate) | |||
if (sampleRate != newRate) | |||
{ | |||
const ScopedLock sl (noteStateLock); | |||
instrument->releaseAllNotes(); | |||
instrument.releaseAllNotes(); | |||
sampleRate = newRate; | |||
} | |||
} | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -52,13 +52,12 @@ public: | |||
/** 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. | |||
If you use this constructor, the synthesiser will use the provided instrument | |||
object 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); | |||
MPESynthesiserBase (MPEInstrument& instrument); | |||
//============================================================================== | |||
/** Returns the synthesiser's internal MPE zone layout. | |||
@@ -200,10 +199,12 @@ protected: | |||
protected: | |||
//============================================================================== | |||
/** @internal */ | |||
std::unique_ptr<MPEInstrument> instrument; | |||
MPEInstrument& instrument; | |||
private: | |||
//============================================================================== | |||
MPEInstrument defaultInstrument { MPEZone (MPEZone::Type::lower, 15) }; | |||
CriticalSection noteStateLock; | |||
double sampleRate = 0.0; | |||
int minimumSubBlockSize = 32; | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -52,25 +52,25 @@ int MPEChannelAssigner::findMidiChannelForNewNote (int noteNumber) noexcept | |||
if (numChannels <= 1) | |||
return firstChannel; | |||
for (auto ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement) | |||
for (int ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement) | |||
{ | |||
if (midiChannels[ch].isFree() && midiChannels[ch].lastNotePlayed == noteNumber) | |||
if (midiChannels[(size_t) ch].isFree() && midiChannels[(size_t) ch].lastNotePlayed == noteNumber) | |||
{ | |||
midiChannelLastAssigned = ch; | |||
midiChannels[ch].notes.add (noteNumber); | |||
midiChannels[(size_t) ch].notes.add (noteNumber); | |||
return ch; | |||
} | |||
} | |||
for (auto ch = midiChannelLastAssigned + channelIncrement; ; ch += channelIncrement) | |||
for (int ch = midiChannelLastAssigned + channelIncrement; ; ch += channelIncrement) | |||
{ | |||
if (ch == lastChannel + channelIncrement) // loop wrap-around | |||
ch = firstChannel; | |||
if (midiChannels[ch].isFree()) | |||
if (midiChannels[(size_t) ch].isFree()) | |||
{ | |||
midiChannelLastAssigned = ch; | |||
midiChannels[ch].notes.add (noteNumber); | |||
midiChannels[(size_t) ch].notes.add (noteNumber); | |||
return ch; | |||
} | |||
@@ -79,11 +79,21 @@ int MPEChannelAssigner::findMidiChannelForNewNote (int noteNumber) noexcept | |||
} | |||
midiChannelLastAssigned = findMidiChannelPlayingClosestNonequalNote (noteNumber); | |||
midiChannels[midiChannelLastAssigned].notes.add (noteNumber); | |||
midiChannels[(size_t) midiChannelLastAssigned].notes.add (noteNumber); | |||
return midiChannelLastAssigned; | |||
} | |||
int MPEChannelAssigner::findMidiChannelForExistingNote (int noteNumber) noexcept | |||
{ | |||
const auto iter = std::find_if (midiChannels.cbegin(), midiChannels.cend(), [&] (auto& ch) | |||
{ | |||
return std::find (ch.notes.begin(), ch.notes.end(), noteNumber) != ch.notes.end(); | |||
}); | |||
return iter != midiChannels.cend() ? (int) std::distance (midiChannels.cbegin(), iter) : -1; | |||
} | |||
void MPEChannelAssigner::noteOff (int noteNumber, int midiChannel) | |||
{ | |||
const auto removeNote = [] (MidiChannel& ch, int noteNum) | |||
@@ -99,7 +109,7 @@ void MPEChannelAssigner::noteOff (int noteNumber, int midiChannel) | |||
if (midiChannel >= 0 && midiChannel <= 16) | |||
{ | |||
removeNote (midiChannels[midiChannel], noteNumber); | |||
removeNote (midiChannels[(size_t) midiChannel], noteNumber); | |||
return; | |||
} | |||
@@ -126,9 +136,9 @@ int MPEChannelAssigner::findMidiChannelPlayingClosestNonequalNote (int noteNumbe | |||
auto channelWithClosestNote = firstChannel; | |||
int closestNoteDistance = 127; | |||
for (auto ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement) | |||
for (int ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement) | |||
{ | |||
for (auto note : midiChannels[ch].notes) | |||
for (auto note : midiChannels[(size_t) ch].notes) | |||
{ | |||
auto noteDistance = std::abs (note - noteNumber); | |||
@@ -296,24 +306,35 @@ struct MPEUtilsUnitTests : public UnitTest | |||
// check that channels are assigned in correct order | |||
int noteNum = 60; | |||
for (int ch = 2; ch <= 16; ++ch) | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch); | |||
{ | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum), ch); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (noteNum), ch); | |||
++noteNum; | |||
} | |||
// check that note-offs are processed | |||
channelAssigner.noteOff (60); | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (60), 2); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (60), 2); | |||
channelAssigner.noteOff (61); | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (61), 3); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (61), 3); | |||
// check that assigned channel was last to play note | |||
channelAssigner.noteOff (65); | |||
channelAssigner.noteOff (66); | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 8); | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (66), 8); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (65), 7); | |||
// find closest channel playing nonequal note | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16); | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (80), 16); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (55), 2); | |||
// all notes off | |||
channelAssigner.allNotesOff(); | |||
@@ -323,10 +344,16 @@ struct MPEUtilsUnitTests : public UnitTest | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7); | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16); | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (66), 8); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (65), 7); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (80), 16); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (55), 2); | |||
// normal assignment | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (101), 3); | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (20), 4); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (101), 3); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (20), 4); | |||
} | |||
// upper | |||
@@ -339,24 +366,35 @@ struct MPEUtilsUnitTests : public UnitTest | |||
// check that channels are assigned in correct order | |||
int noteNum = 60; | |||
for (int ch = 15; ch >= 1; --ch) | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch); | |||
{ | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum), ch); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (noteNum), ch); | |||
++noteNum; | |||
} | |||
// check that note-offs are processed | |||
channelAssigner.noteOff (60); | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (60), 15); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (60), 15); | |||
channelAssigner.noteOff (61); | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (61), 14); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (61), 14); | |||
// check that assigned channel was last to play note | |||
channelAssigner.noteOff (65); | |||
channelAssigner.noteOff (66); | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 9); | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (66), 9); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (65), 10); | |||
// find closest channel playing nonequal note | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1); | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (80), 1); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (55), 15); | |||
// all notes off | |||
channelAssigner.allNotesOff(); | |||
@@ -366,10 +404,16 @@ struct MPEUtilsUnitTests : public UnitTest | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10); | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1); | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (66), 9); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (65), 10); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (80), 1); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (55), 15); | |||
// normal assignment | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (101), 14); | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (20), 13); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (101), 14); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (20), 13); | |||
} | |||
// legacy | |||
@@ -379,24 +423,35 @@ struct MPEUtilsUnitTests : public UnitTest | |||
// check that channels are assigned in correct order | |||
int noteNum = 60; | |||
for (int ch = 1; ch <= 16; ++ch) | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch); | |||
{ | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum), ch); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (noteNum), ch); | |||
++noteNum; | |||
} | |||
// check that note-offs are processed | |||
channelAssigner.noteOff (60); | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (60), 1); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (60), 1); | |||
channelAssigner.noteOff (61); | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (61), 2); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (61), 2); | |||
// check that assigned channel was last to play note | |||
channelAssigner.noteOff (65); | |||
channelAssigner.noteOff (66); | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 7); | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 6); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (66), 7); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (65), 6); | |||
// find closest channel playing nonequal note | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16); | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 1); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (80), 16); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (55), 1); | |||
// all notes off | |||
channelAssigner.allNotesOff(); | |||
@@ -406,10 +461,16 @@ struct MPEUtilsUnitTests : public UnitTest | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 6); | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16); | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 1); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (66), 7); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (65), 6); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (80), 16); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (55), 1); | |||
// normal assignment | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (101), 2); | |||
expectEquals (channelAssigner.findMidiChannelForNewNote (20), 3); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (101), 2); | |||
expectEquals (channelAssigner.findMidiChannelForExistingNote (20), 3); | |||
} | |||
} | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -63,6 +63,11 @@ public: | |||
*/ | |||
int findMidiChannelForNewNote (int noteNumber) noexcept; | |||
/** If a note has been added using findMidiChannelForNewNote() this will return the channel | |||
to which it was assigned, otherwise it will return -1. | |||
*/ | |||
int findMidiChannelForExistingNote (int initialNoteOnNumber) noexcept; | |||
/** You must call this method for all note-offs that you receive so that this class | |||
can keep track of the currently playing notes internally. | |||
@@ -86,7 +91,7 @@ private: | |||
int lastNotePlayed = -1; | |||
bool isFree() const noexcept { return notes.isEmpty(); } | |||
}; | |||
MidiChannel midiChannels[17]; | |||
std::array<MidiChannel, 17> midiChannels; | |||
//============================================================================== | |||
int findMidiChannelPlayingClosestNonequalNote (int noteNumber) noexcept; | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -43,6 +43,18 @@ MPEValue MPEValue::from14BitInt (int value) noexcept | |||
return { value }; | |||
} | |||
MPEValue MPEValue::fromUnsignedFloat (float value) noexcept | |||
{ | |||
jassert (0.0f <= value && value <= 1.0f); | |||
return { roundToInt (value * 16383.0f) }; | |||
} | |||
MPEValue MPEValue::fromSignedFloat (float value) noexcept | |||
{ | |||
jassert (-1.0f <= value && value <= 1.0f); | |||
return { roundToInt (((value + 1.0f) * 16383.0f) / 2.0f) }; | |||
} | |||
//============================================================================== | |||
MPEValue MPEValue::minValue() noexcept { return MPEValue::from7BitInt (0); } | |||
MPEValue MPEValue::centreValue() noexcept { return MPEValue::from7BitInt (64); } | |||
@@ -121,26 +133,34 @@ public: | |||
beginTest ("zero/minimum value"); | |||
{ | |||
expectValuesConsistent (MPEValue::from7BitInt (0), 0, 0, -1.0f, 0.0f); | |||
expectValuesConsistent (MPEValue::from14BitInt (0), 0, 0, -1.0f, 0.0f); | |||
expectValuesConsistent (MPEValue::from7BitInt (0), 0, 0, -1.0f, 0.0f); | |||
expectValuesConsistent (MPEValue::from14BitInt (0), 0, 0, -1.0f, 0.0f); | |||
expectValuesConsistent (MPEValue::fromUnsignedFloat (0.0f), 0, 0, -1.0f, 0.0f); | |||
expectValuesConsistent (MPEValue::fromSignedFloat (-1.0f), 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); | |||
expectValuesConsistent (MPEValue::from7BitInt (127), 127, 16383, 1.0f, 1.0f); | |||
expectValuesConsistent (MPEValue::from14BitInt (16383), 127, 16383, 1.0f, 1.0f); | |||
expectValuesConsistent (MPEValue::fromUnsignedFloat (1.0f), 127, 16383, 1.0f, 1.0f); | |||
expectValuesConsistent (MPEValue::fromSignedFloat (1.0f), 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); | |||
expectValuesConsistent (MPEValue::from7BitInt (64), 64, 8192, 0.0f, 0.5f); | |||
expectValuesConsistent (MPEValue::from14BitInt (8192), 64, 8192, 0.0f, 0.5f); | |||
expectValuesConsistent (MPEValue::fromUnsignedFloat (0.5f), 64, 8192, 0.0f, 0.5f); | |||
expectValuesConsistent (MPEValue::fromSignedFloat (0.0f), 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); | |||
expectValuesConsistent (MPEValue::from7BitInt (32), 32, 4096, -0.5f, 0.25f); | |||
expectValuesConsistent (MPEValue::from14BitInt (4096), 32, 4096, -0.5f, 0.25f); | |||
expectValuesConsistent (MPEValue::fromUnsignedFloat (0.25f), 32, 4096, -0.5f, 0.25f); | |||
expectValuesConsistent (MPEValue::fromSignedFloat (-0.5f), 32, 4096, -0.5f, 0.25f); | |||
} | |||
} | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -53,6 +53,12 @@ public: | |||
*/ | |||
static MPEValue from14BitInt (int value) noexcept; | |||
/** Constructs an MPEValue from a float between 0.0f and 1.0f. */ | |||
static MPEValue fromUnsignedFloat (float value) noexcept; | |||
/** Constructs an MPEValue from a float between -1.0f and 1.0f. */ | |||
static MPEValue fromSignedFloat (float value) noexcept; | |||
/** Constructs an MPEValue corresponding to the centre value. */ | |||
static MPEValue centreValue() noexcept; | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -23,7 +23,17 @@ | |||
namespace juce | |||
{ | |||
MPEZoneLayout::MPEZoneLayout() noexcept {} | |||
MPEZoneLayout::MPEZoneLayout (MPEZone lower, MPEZone upper) | |||
: lowerZone (lower), upperZone (upper) | |||
{ | |||
} | |||
MPEZoneLayout::MPEZoneLayout (MPEZone zone) | |||
: lowerZone (zone.isLowerZone() ? zone : MPEZone()), | |||
upperZone (! zone.isLowerZone() ? zone : MPEZone()) | |||
{ | |||
} | |||
MPEZoneLayout::MPEZoneLayout (const MPEZoneLayout& other) | |||
: lowerZone (other.lowerZone), | |||
@@ -54,9 +64,9 @@ void MPEZoneLayout::setZone (bool isLower, int numMemberChannels, int perNotePit | |||
checkAndLimitZoneParameters (0, 96, masterPitchbendRange); | |||
if (isLower) | |||
lowerZone = { true, numMemberChannels, perNotePitchbendRange, masterPitchbendRange }; | |||
lowerZone = { MPEZone::Type::lower, numMemberChannels, perNotePitchbendRange, masterPitchbendRange }; | |||
else | |||
upperZone = { false, numMemberChannels, perNotePitchbendRange, masterPitchbendRange }; | |||
upperZone = { MPEZone::Type::upper, numMemberChannels, perNotePitchbendRange, masterPitchbendRange }; | |||
if (numMemberChannels > 0) | |||
{ | |||
@@ -86,8 +96,8 @@ void MPEZoneLayout::setUpperZone (int numMemberChannels, int perNotePitchbendRan | |||
void MPEZoneLayout::clearAllZones() | |||
{ | |||
lowerZone = { true, 0 }; | |||
upperZone = { false, 0 }; | |||
lowerZone = { MPEZone::Type::lower, 0 }; | |||
upperZone = { MPEZone::Type::upper, 0 }; | |||
sendLayoutChangeMessage(); | |||
} | |||
@@ -128,7 +138,7 @@ void MPEZoneLayout::processZoneLayoutRpnMessage (MidiRPNMessage rpn) | |||
} | |||
} | |||
void MPEZoneLayout::updateMasterPitchbend (Zone& zone, int value) | |||
void MPEZoneLayout::updateMasterPitchbend (MPEZone& zone, int value) | |||
{ | |||
if (zone.masterPitchbendRange != value) | |||
{ | |||
@@ -138,7 +148,7 @@ void MPEZoneLayout::updateMasterPitchbend (Zone& zone, int value) | |||
} | |||
} | |||
void MPEZoneLayout::updatePerNotePitchbendRange (Zone& zone, int value) | |||
void MPEZoneLayout::updatePerNotePitchbendRange (MPEZone& zone, int value) | |||
{ | |||
if (zone.perNotePitchbendRange != value) | |||
{ | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -23,6 +23,82 @@ | |||
namespace juce | |||
{ | |||
//============================================================================== | |||
/** | |||
This struct represents an MPE zone. | |||
It can either be a lower or an upper zone, where: | |||
- A lower zone encompasses master channel 1 and an arbitrary number of ascending | |||
MIDI channels, increasing from channel 2. | |||
- An upper zone encompasses master channel 16 and an arbitrary number of descending | |||
MIDI channels, decreasing from channel 15. | |||
It also defines a pitchbend range (in semitones) to be applied for per-note pitchbends and | |||
master pitchbends, respectively. | |||
*/ | |||
struct MPEZone | |||
{ | |||
enum class Type { lower, upper }; | |||
MPEZone() = default; | |||
MPEZone (Type type, int memberChannels = 0, int perNotePitchbend = 48, int masterPitchbend = 2) | |||
: zoneType (type), | |||
numMemberChannels (memberChannels), | |||
perNotePitchbendRange (perNotePitchbend), | |||
masterPitchbendRange (masterPitchbend) | |||
{} | |||
bool isLowerZone() const noexcept { return zoneType == Type::lower; } | |||
bool isUpperZone() const noexcept { return zoneType == Type::upper; } | |||
bool isActive() const noexcept { return numMemberChannels > 0; } | |||
int getMasterChannel() const noexcept { return isLowerZone() ? lowerZoneMasterChannel : upperZoneMasterChannel; } | |||
int getFirstMemberChannel() const noexcept { return isLowerZone() ? lowerZoneMasterChannel + 1 : upperZoneMasterChannel - 1; } | |||
int getLastMemberChannel() const noexcept { return isLowerZone() ? (lowerZoneMasterChannel + numMemberChannels) | |||
: (upperZoneMasterChannel - numMemberChannels); } | |||
bool isUsingChannelAsMemberChannel (int channel) const noexcept | |||
{ | |||
return isLowerZone() ? (lowerZoneMasterChannel < channel && channel <= getLastMemberChannel()) | |||
: (channel < upperZoneMasterChannel && getLastMemberChannel() <= channel); | |||
} | |||
bool isUsing (int channel) const noexcept | |||
{ | |||
return isUsingChannelAsMemberChannel (channel) || channel == getMasterChannel(); | |||
} | |||
static auto tie (const MPEZone& z) | |||
{ | |||
return std::tie (z.zoneType, | |||
z.numMemberChannels, | |||
z.perNotePitchbendRange, | |||
z.masterPitchbendRange); | |||
} | |||
bool operator== (const MPEZone& other) const | |||
{ | |||
return tie (*this) == tie (other); | |||
} | |||
bool operator!= (const MPEZone& other) const | |||
{ | |||
return tie (*this) != tie (other); | |||
} | |||
//============================================================================== | |||
static constexpr int lowerZoneMasterChannel = 1, | |||
upperZoneMasterChannel = 16; | |||
Type zoneType = Type::lower; | |||
int numMemberChannels = 0; | |||
int perNotePitchbendRange = 48; | |||
int masterPitchbendRange = 2; | |||
}; | |||
//============================================================================== | |||
/** | |||
This class represents the current MPE zone layout of a device capable of handling MPE. | |||
@@ -44,89 +120,28 @@ namespace juce | |||
class JUCE_API MPEZoneLayout | |||
{ | |||
public: | |||
/** Default constructor. | |||
//============================================================================== | |||
/** Creates a layout with inactive upper and lower zones. */ | |||
MPEZoneLayout() = default; | |||
This will create a layout with inactive lower and upper zones, representing | |||
a device with MPE mode disabled. | |||
/** Creates a layout with the given upper and lower zones. */ | |||
MPEZoneLayout (MPEZone lower, MPEZone upper); | |||
You can set the lower or upper MPE zones using the setZone() method. | |||
/** Creates a layout with a single upper or lower zone, leaving the other zone uninitialised. */ | |||
MPEZoneLayout (MPEZone singleZone); | |||
@see setZone | |||
*/ | |||
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); | |||
//============================================================================== | |||
/** | |||
This struct represents an MPE zone. | |||
It can either be a lower or an upper zone, where: | |||
- A lower zone encompasses master channel 1 and an arbitrary number of ascending | |||
MIDI channels, increasing from channel 2. | |||
- An upper zone encompasses master channel 16 and an arbitrary number of descending | |||
MIDI channels, decreasing from channel 15. | |||
It also defines a pitchbend range (in semitones) to be applied for per-note pitchbends and | |||
master pitchbends, respectively. | |||
*/ | |||
struct Zone | |||
{ | |||
Zone (const Zone& other) = default; | |||
bool isLowerZone() const noexcept { return lowerZone; } | |||
bool isUpperZone() const noexcept { return ! lowerZone; } | |||
bool isActive() const noexcept { return numMemberChannels > 0; } | |||
int getMasterChannel() const noexcept { return lowerZone ? 1 : 16; } | |||
int getFirstMemberChannel() const noexcept { return lowerZone ? 2 : 15; } | |||
int getLastMemberChannel() const noexcept { return lowerZone ? (1 + numMemberChannels) | |||
: (16 - numMemberChannels); } | |||
bool isUsingChannelAsMemberChannel (int channel) const noexcept | |||
{ | |||
return lowerZone ? (channel > 1 && channel <= 1 + numMemberChannels) | |||
: (channel < 16 && channel >= 16 - numMemberChannels); | |||
} | |||
bool isUsing (int channel) const noexcept | |||
{ | |||
return isUsingChannelAsMemberChannel (channel) || channel == getMasterChannel(); | |||
} | |||
bool operator== (const MPEZoneLayout& other) const { return lowerZone == other.lowerZone && upperZone == other.upperZone; } | |||
bool operator!= (const MPEZoneLayout& other) const { return ! operator== (other); } | |||
bool operator== (const Zone& other) const noexcept { return lowerZone == other.lowerZone | |||
&& numMemberChannels == other.numMemberChannels | |||
&& perNotePitchbendRange == other.perNotePitchbendRange | |||
&& masterPitchbendRange == other.masterPitchbendRange; } | |||
bool operator!= (const Zone& other) const noexcept { return ! operator== (other); } | |||
int numMemberChannels; | |||
int perNotePitchbendRange; | |||
int masterPitchbendRange; | |||
private: | |||
friend class MPEZoneLayout; | |||
Zone (bool lower, int memberChans = 0, int perNotePb = 48, int masterPb = 2) noexcept | |||
: numMemberChannels (memberChans), | |||
perNotePitchbendRange (perNotePb), | |||
masterPitchbendRange (masterPb), | |||
lowerZone (lower) | |||
{ | |||
} | |||
//============================================================================== | |||
/** Returns a struct representing the lower MPE zone. */ | |||
MPEZone getLowerZone() const noexcept { return lowerZone; } | |||
bool lowerZone; | |||
}; | |||
/** Returns a struct representing the upper MPE zone. */ | |||
MPEZone getUpperZone() const noexcept { return upperZone; } | |||
/** Sets the lower zone of this layout. */ | |||
void setLowerZone (int numMemberChannels = 0, | |||
@@ -138,17 +153,14 @@ public: | |||
int perNotePitchbendRange = 48, | |||
int masterPitchbendRange = 2) noexcept; | |||
/** Returns a struct representing the lower MPE zone. */ | |||
const Zone getLowerZone() const noexcept { return lowerZone; } | |||
/** Returns a struct representing the upper MPE zone. */ | |||
const Zone getUpperZone() const noexcept { return upperZone; } | |||
/** Clears the lower and upper zones of this layout, making them both inactive | |||
and disabling MPE mode. | |||
*/ | |||
void clearAllZones(); | |||
/** Returns true if either of the zones are active. */ | |||
bool isActive() const { return lowerZone.isActive() || upperZone.isActive(); } | |||
//============================================================================== | |||
/** 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 | |||
@@ -200,10 +212,14 @@ public: | |||
/** Removes a listener. */ | |||
void removeListener (Listener* const listenerToRemove) noexcept; | |||
#ifndef DOXYGEN | |||
using Zone = MPEZone; | |||
#endif | |||
private: | |||
//============================================================================== | |||
Zone lowerZone { true, 0 }; | |||
Zone upperZone { false, 0 }; | |||
MPEZone lowerZone { MPEZone::Type::lower, 0 }; | |||
MPEZone upperZone { MPEZone::Type::upper, 0 }; | |||
MidiRPNDetector rpnDetector; | |||
ListenerList<Listener> listeners; | |||
@@ -215,8 +231,8 @@ private: | |||
void processZoneLayoutRpnMessage (MidiRPNMessage); | |||
void processPitchbendRangeRpnMessage (MidiRPNMessage); | |||
void updateMasterPitchbend (Zone&, int); | |||
void updatePerNotePitchbendRange (Zone&, int); | |||
void updateMasterPitchbend (MPEZone&, int); | |||
void updatePerNotePitchbendRange (MPEZone&, int); | |||
void sendLayoutChangeMessage(); | |||
void checkAndLimitZoneParameters (int, int, int&) noexcept; | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -23,10 +23,140 @@ | |||
namespace juce | |||
{ | |||
#if ! DOXYGEN && (JUCE_MAC || JUCE_IOS) | |||
#if ! defined (DOXYGEN) && (JUCE_MAC || JUCE_IOS) | |||
struct CoreAudioLayouts | |||
{ | |||
//============================================================================== | |||
struct LayoutTagSpeakerList | |||
{ | |||
AudioChannelLayoutTag tag; | |||
AudioChannelSet::ChannelType channelTypes[16]; | |||
}; | |||
//============================================================================== | |||
// 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 | |||
{ | |||
template <typename... Items> | |||
static constexpr auto getArray (Items... items) | |||
{ | |||
return std::array<LayoutTagSpeakerList, sizeof... (items)> { { items... } }; | |||
} | |||
static constexpr auto get() | |||
{ | |||
using List = LayoutTagSpeakerList; | |||
return getArray (List { kAudioChannelLayoutTag_Mono, { centre } }, | |||
List { kAudioChannelLayoutTag_Stereo, { left, right } }, | |||
List { kAudioChannelLayoutTag_MPEG_3_0_A, { left, right, centre } }, | |||
List { kAudioChannelLayoutTag_ITU_2_1, { left, right, centreSurround } }, | |||
List { kAudioChannelLayoutTag_MPEG_4_0_A, { left, right, centre, centreSurround } }, | |||
List { kAudioChannelLayoutTag_MPEG_5_0_A, { left, right, centre, leftSurround, rightSurround } }, | |||
List { kAudioChannelLayoutTag_MPEG_5_1_A, { left, right, centre, LFE, leftSurround, rightSurround } }, | |||
List { kAudioChannelLayoutTag_AudioUnit_6_0, { left, right, leftSurround, rightSurround, centre, centreSurround } }, | |||
List { kAudioChannelLayoutTag_MPEG_6_1_A, { left, right, centre, LFE, leftSurround, rightSurround, centreSurround } }, | |||
List { kAudioChannelLayoutTag_DTS_6_0_A, { leftSurroundSide, rightSurroundSide, left, right, leftSurround, rightSurround } }, | |||
List { kAudioChannelLayoutTag_DTS_6_1_A, { leftSurroundSide, rightSurroundSide, left, right, leftSurround, rightSurround, LFE } }, | |||
List { kAudioChannelLayoutTag_AudioUnit_7_0, { left, right, leftSurroundSide, rightSurroundSide, centre, leftSurroundRear, rightSurroundRear } }, | |||
List { kAudioChannelLayoutTag_AudioUnit_7_0_Front, { left, right, leftSurround, rightSurround, centre, leftCentre, rightCentre } }, | |||
List { kAudioChannelLayoutTag_MPEG_7_1_C, { left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear } }, | |||
List { kAudioChannelLayoutTag_MPEG_7_1_A, { left, right, centre, LFE, leftSurround, rightSurround, leftCentre, rightCentre } }, | |||
List { kAudioChannelLayoutTag_Ambisonic_B_Format, { ambisonicW, ambisonicX, ambisonicY, ambisonicZ } }, | |||
List { kAudioChannelLayoutTag_Quadraphonic, { left, right, leftSurround, rightSurround } }, | |||
List { kAudioChannelLayoutTag_Pentagonal, { left, right, leftSurroundRear, rightSurroundRear, centre } }, | |||
List { kAudioChannelLayoutTag_Hexagonal, { left, right, leftSurroundRear, rightSurroundRear, centre, centreSurround } }, | |||
List { kAudioChannelLayoutTag_Octagonal, { left, right, leftSurround, rightSurround, centre, centreSurround, wideLeft, wideRight } }, | |||
#if defined (MAC_OS_VERSION_11_0) | |||
List { kAudioChannelLayoutTag_Atmos_5_1_4, { left, right, centre, LFE, leftSurround, rightSurround, topFrontLeft, topFrontRight, topRearLeft, topRearRight } }, | |||
List { kAudioChannelLayoutTag_Atmos_7_1_2, { left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topSideLeft, topSideRight } }, | |||
#endif | |||
#if defined (MAC_OS_X_VERSION_10_15) | |||
List { kAudioChannelLayoutTag_Atmos_5_1_2, { left, right, centre, LFE, leftSurround, rightSurround, topSideLeft, topSideRight } }, | |||
List { kAudioChannelLayoutTag_Atmos_7_1_4, { left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topFrontLeft, topFrontRight, topRearLeft, topRearRight } }, | |||
List { kAudioChannelLayoutTag_Atmos_9_1_6, { left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, wideLeft, wideRight, topFrontLeft, topFrontRight, topSideLeft, topSideRight, topRearLeft, topRearRight } }, | |||
#endif | |||
// More uncommon layouts... | |||
List { kAudioChannelLayoutTag_StereoHeadphones, { left, right } }, | |||
List { kAudioChannelLayoutTag_MatrixStereo, { left, right } }, | |||
List { kAudioChannelLayoutTag_MidSide, { centre, discreteChannel0 } }, | |||
List { kAudioChannelLayoutTag_XY, { ambisonicX, ambisonicY } }, | |||
List { kAudioChannelLayoutTag_Binaural, { left, right } }, | |||
List { kAudioChannelLayoutTag_Cube, { left, right, leftSurround, rightSurround, topFrontLeft, topFrontRight, topRearLeft, topRearRight } }, | |||
List { kAudioChannelLayoutTag_MPEG_3_0_B, { centre, left, right } }, | |||
List { kAudioChannelLayoutTag_MPEG_4_0_B, { centre, left, right, centreSurround } }, | |||
List { kAudioChannelLayoutTag_MPEG_5_0_B, { left, right, leftSurround, rightSurround, centre } }, | |||
List { kAudioChannelLayoutTag_MPEG_5_0_C, { left, centre, right, leftSurround, rightSurround } }, | |||
List { kAudioChannelLayoutTag_MPEG_5_0_D, { centre, left, right, leftSurround, rightSurround } }, | |||
List { kAudioChannelLayoutTag_MPEG_5_1_B, { left, right, leftSurround, rightSurround, centre, LFE } }, | |||
List { kAudioChannelLayoutTag_MPEG_5_1_C, { left, centre, right, leftSurround, rightSurround, LFE } }, | |||
List { kAudioChannelLayoutTag_MPEG_5_1_D, { centre, left, right, leftSurround, rightSurround, LFE } }, | |||
List { kAudioChannelLayoutTag_MPEG_7_1_B, { centre, leftCentre, rightCentre, left, right, leftSurround, rightSurround, LFE } }, | |||
List { kAudioChannelLayoutTag_Emagic_Default_7_1, { left, right, leftSurround, rightSurround, centre, LFE, leftCentre, rightCentre } }, | |||
List { kAudioChannelLayoutTag_SMPTE_DTV, { left, right, centre, LFE, leftSurround, rightSurround, discreteChannel0 /* leftMatrixTotal */, (ChannelType) (discreteChannel0 + 1) /* rightMatrixTotal */} }, | |||
List { kAudioChannelLayoutTag_ITU_2_2, { left, right, leftSurround, rightSurround } }, | |||
List { kAudioChannelLayoutTag_DVD_4, { left, right, LFE } }, | |||
List { kAudioChannelLayoutTag_DVD_5, { left, right, LFE, centreSurround } }, | |||
List { kAudioChannelLayoutTag_DVD_6, { left, right, LFE, leftSurround, rightSurround } }, | |||
List { kAudioChannelLayoutTag_DVD_10, { left, right, centre, LFE } }, | |||
List { kAudioChannelLayoutTag_DVD_11, { left, right, centre, LFE, centreSurround } }, | |||
List { kAudioChannelLayoutTag_DVD_18, { left, right, leftSurround, rightSurround, LFE } }, | |||
List { kAudioChannelLayoutTag_AAC_6_0, { centre, left, right, leftSurround, rightSurround, centreSurround } }, | |||
List { kAudioChannelLayoutTag_AAC_6_1, { centre, left, right, leftSurround, rightSurround, centreSurround, LFE } }, | |||
List { kAudioChannelLayoutTag_AAC_7_0, { centre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear } }, | |||
List { kAudioChannelLayoutTag_AAC_7_1_B, { centre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, LFE } }, | |||
List { kAudioChannelLayoutTag_AAC_7_1_C, { centre, left, right, leftSurround, rightSurround, LFE, topFrontLeft, topFrontRight } }, | |||
List { kAudioChannelLayoutTag_AAC_Octagonal, { centre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, centreSurround } }, | |||
List { kAudioChannelLayoutTag_TMH_10_2_std, { left, right, centre, topFrontCentre, leftSurroundSide, rightSurroundSide, leftSurround, rightSurround, topFrontLeft, topFrontRight, wideLeft, wideRight, topRearCentre, centreSurround, LFE, LFE2 } }, | |||
List { kAudioChannelLayoutTag_AC3_1_0_1, { centre, LFE } }, | |||
List { kAudioChannelLayoutTag_AC3_3_0, { left, centre, right } }, | |||
List { kAudioChannelLayoutTag_AC3_3_1, { left, centre, right, centreSurround } }, | |||
List { kAudioChannelLayoutTag_AC3_3_0_1, { left, centre, right, LFE } }, | |||
List { kAudioChannelLayoutTag_AC3_2_1_1, { left, right, centreSurround, LFE } }, | |||
List { kAudioChannelLayoutTag_AC3_3_1_1, { left, centre, right, centreSurround, LFE } }, | |||
List { kAudioChannelLayoutTag_EAC_6_0_A, { left, centre, right, leftSurround, rightSurround, centreSurround } }, | |||
List { kAudioChannelLayoutTag_EAC_7_0_A, { left, centre, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear } }, | |||
List { kAudioChannelLayoutTag_EAC3_6_1_A, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround } }, | |||
List { kAudioChannelLayoutTag_EAC3_6_1_B, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround } }, | |||
List { kAudioChannelLayoutTag_EAC3_6_1_C, { left, centre, right, leftSurround, rightSurround, LFE, topFrontCentre } }, | |||
List { kAudioChannelLayoutTag_EAC3_7_1_A, { left, centre, right, leftSurround, rightSurround, LFE, leftSurroundRear, rightSurroundRear } }, | |||
List { kAudioChannelLayoutTag_EAC3_7_1_B, { left, centre, right, leftSurround, rightSurround, LFE, leftCentre, rightCentre } }, | |||
List { kAudioChannelLayoutTag_EAC3_7_1_C, { left, centre, right, leftSurround, rightSurround, LFE, leftSurroundSide, rightSurroundSide } }, | |||
List { kAudioChannelLayoutTag_EAC3_7_1_D, { left, centre, right, leftSurround, rightSurround, LFE, wideLeft, wideRight } }, | |||
List { kAudioChannelLayoutTag_EAC3_7_1_E, { left, centre, right, leftSurround, rightSurround, LFE, topFrontLeft, topFrontRight } }, | |||
List { kAudioChannelLayoutTag_EAC3_7_1_F, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround, topMiddle } }, | |||
List { kAudioChannelLayoutTag_EAC3_7_1_G, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround, topFrontCentre } }, | |||
List { kAudioChannelLayoutTag_EAC3_7_1_H, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround, topFrontCentre } }, | |||
List { kAudioChannelLayoutTag_DTS_3_1, { centre, left, right, LFE } }, | |||
List { kAudioChannelLayoutTag_DTS_4_1, { centre, left, right, centreSurround, LFE } }, | |||
List { kAudioChannelLayoutTag_DTS_6_0_B, { centre, left, right, leftSurroundRear, rightSurroundRear, centreSurround } }, | |||
List { kAudioChannelLayoutTag_DTS_6_0_C, { centre, centreSurround, left, right, leftSurroundRear, rightSurroundRear } }, | |||
List { kAudioChannelLayoutTag_DTS_6_1_B, { centre, left, right, leftSurroundRear, rightSurroundRear, centreSurround, LFE } }, | |||
List { kAudioChannelLayoutTag_DTS_6_1_C, { centre, centreSurround, left, right, leftSurroundRear, rightSurroundRear, LFE } }, | |||
List { kAudioChannelLayoutTag_DTS_6_1_D, { centre, left, right, leftSurround, rightSurround, LFE, centreSurround } }, | |||
List { kAudioChannelLayoutTag_DTS_7_0, { leftCentre, centre, rightCentre, left, right, leftSurround, rightSurround } }, | |||
List { kAudioChannelLayoutTag_DTS_7_1, { leftCentre, centre, rightCentre, left, right, leftSurround, rightSurround, LFE } }, | |||
List { kAudioChannelLayoutTag_DTS_8_0_A, { leftCentre, rightCentre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear } }, | |||
List { kAudioChannelLayoutTag_DTS_8_0_B, { leftCentre, centre, rightCentre, left, right, leftSurround, centreSurround, rightSurround } }, | |||
List { kAudioChannelLayoutTag_DTS_8_1_A, { leftCentre, rightCentre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, LFE } }, | |||
List { kAudioChannelLayoutTag_DTS_8_1_B, { leftCentre, centre, rightCentre, left, right, leftSurround, centreSurround, rightSurround, LFE } }); | |||
} | |||
}; | |||
public: | |||
//============================================================================== | |||
enum | |||
{ | |||
@@ -61,16 +191,16 @@ struct CoreAudioLayouts | |||
if (set.getAmbisonicOrder() >= 0) | |||
return coreAudioHOASN3DLayoutTag | static_cast<unsigned> (set.size()); | |||
for (auto* tbl = SpeakerLayoutTable::get(); tbl->tag != 0; ++tbl) | |||
for (const auto& item : SpeakerLayoutTable::get()) | |||
{ | |||
AudioChannelSet caSet; | |||
for (int i = 0; i < numElementsInArray (tbl->channelTypes) | |||
&& tbl->channelTypes[i] != AudioChannelSet::unknown; ++i) | |||
caSet.addChannel (tbl->channelTypes[i]); | |||
for (int i = 0; i < numElementsInArray (item.channelTypes) | |||
&& item.channelTypes[i] != AudioChannelSet::unknown; ++i) | |||
caSet.addChannel (item.channelTypes[i]); | |||
if (caSet == set) | |||
return tbl->tag; | |||
return item.tag; | |||
} | |||
return kAudioChannelLayoutTag_DiscreteInOrder | static_cast<AudioChannelLayoutTag> (set.size()); | |||
@@ -121,13 +251,13 @@ struct CoreAudioLayouts | |||
Array<AudioChannelSet::ChannelType> speakers; | |||
for (auto* tbl = SpeakerLayoutTable::get(); tbl->tag != 0; ++tbl) | |||
for (const auto& item : SpeakerLayoutTable::get()) | |||
{ | |||
if (tag == tbl->tag) | |||
if (tag == item.tag) | |||
{ | |||
for (int i = 0; i < numElementsInArray (tbl->channelTypes) | |||
&& tbl->channelTypes[i] != AudioChannelSet::unknown; ++i) | |||
speakers.add (tbl->channelTypes[i]); | |||
for (int i = 0; i < numElementsInArray (item.channelTypes) | |||
&& item.channelTypes[i] != AudioChannelSet::unknown; ++i) | |||
speakers.add (item.channelTypes[i]); | |||
return speakers; | |||
} | |||
@@ -150,19 +280,12 @@ struct CoreAudioLayouts | |||
} | |||
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); | |||
for (const auto& item : SpeakerLayoutTable::get()) | |||
tags.addIfNotAlreadyThere (item.tag); | |||
for (unsigned order = 0; order <= 5; ++order) | |||
tags.addIfNotAlreadyThere (coreAudioHOASN3DLayoutTag | ((order + 1) * (order + 1))); | |||
@@ -170,117 +293,6 @@ private: | |||
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 } }, | |||
#if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_8 | |||
{ 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 } }, | |||
#endif | |||
{ 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 | |||
{ | |||
@@ -0,0 +1,80 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
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. | |||
============================================================================== | |||
*/ | |||
// This file will be included directly by macOS/iOS-specific .cpps | |||
#pragma once | |||
#if ! DOXYGEN | |||
#include <mach/mach_time.h> | |||
namespace juce | |||
{ | |||
struct CoreAudioTimeConversions | |||
{ | |||
public: | |||
CoreAudioTimeConversions() | |||
{ | |||
mach_timebase_info_data_t info{}; | |||
mach_timebase_info (&info); | |||
numerator = info.numer; | |||
denominator = info.denom; | |||
} | |||
uint64_t hostTimeToNanos (uint64_t hostTime) const | |||
{ | |||
return multiplyByRatio (hostTime, numerator, denominator); | |||
} | |||
uint64_t nanosToHostTime (uint64_t nanos) const | |||
{ | |||
return multiplyByRatio (nanos, denominator, numerator); | |||
} | |||
private: | |||
// Adapted from CAHostTimeBase.h in the Core Audio Utility Classes | |||
static uint64_t multiplyByRatio (uint64_t toMultiply, uint64_t numerator, uint64_t denominator) | |||
{ | |||
#if defined (__SIZEOF_INT128__) | |||
unsigned __int128 | |||
#else | |||
long double | |||
#endif | |||
result = toMultiply; | |||
if (numerator != denominator) | |||
{ | |||
result *= numerator; | |||
result /= denominator; | |||
} | |||
return (uint64_t) result; | |||
} | |||
uint64_t numerator = 0, denominator = 0; | |||
}; | |||
} // namespace juce | |||
#endif |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -65,6 +65,8 @@ void BufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double ne | |||
buffer.setSize (numberOfChannels, bufferSizeNeeded); | |||
buffer.clear(); | |||
const ScopedLock sl (bufferRangeLock); | |||
bufferValidStart = 0; | |||
bufferValidEnd = 0; | |||
@@ -72,6 +74,8 @@ void BufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double ne | |||
do | |||
{ | |||
const ScopedUnlock ul (bufferRangeLock); | |||
backgroundThread.moveToFrontOfQueue (this); | |||
Thread::sleep (5); | |||
} | |||
@@ -96,100 +100,97 @@ void BufferingAudioSource::releaseResources() | |||
void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) | |||
{ | |||
const ScopedLock sl (bufferStartPosLock); | |||
auto start = bufferValidStart.load(); | |||
auto end = bufferValidEnd.load(); | |||
auto pos = nextPlayPos.load(); | |||
auto validStart = (int) (jlimit (start, end, pos) - pos); | |||
auto validEnd = (int) (jlimit (start, end, pos + info.numSamples) - pos); | |||
const auto bufferRange = getValidBufferRange (info.numSamples); | |||
if (validStart == validEnd) | |||
if (bufferRange.isEmpty()) | |||
{ | |||
// total cache miss | |||
info.clearActiveBufferRegion(); | |||
return; | |||
} | |||
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 | |||
const auto validStart = bufferRange.getStart(); | |||
const auto validEnd = bufferRange.getEnd(); | |||
const ScopedLock sl (callbackLock); | |||
if (validStart > 0) | |||
info.buffer->clear (info.startSample, validStart); // partial cache miss at start | |||
if (validStart < validEnd) | |||
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;) | |||
{ | |||
for (int chan = jmin (numberOfChannels, info.buffer->getNumChannels()); --chan >= 0;) | |||
jassert (buffer.getNumSamples() > 0); | |||
const auto startBufferIndex = (int) ((validStart + nextPlayPos) % buffer.getNumSamples()); | |||
const auto endBufferIndex = (int) ((validEnd + nextPlayPos) % buffer.getNumSamples()); | |||
if (startBufferIndex < endBufferIndex) | |||
{ | |||
jassert (buffer.getNumSamples() > 0); | |||
auto startBufferIndex = (int) ((validStart + nextPlayPos) % buffer.getNumSamples()); | |||
auto endBufferIndex = (int) ((validEnd + nextPlayPos) % buffer.getNumSamples()); | |||
if (startBufferIndex < endBufferIndex) | |||
{ | |||
info.buffer->copyFrom (chan, info.startSample + validStart, | |||
buffer, | |||
chan, startBufferIndex, | |||
validEnd - validStart); | |||
} | |||
else | |||
{ | |||
auto 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); | |||
} | |||
info.buffer->copyFrom (chan, info.startSample + validStart, | |||
buffer, | |||
chan, startBufferIndex, | |||
validEnd - validStart); | |||
} | |||
} | |||
else | |||
{ | |||
const auto initialSize = buffer.getNumSamples() - startBufferIndex; | |||
nextPlayPos += info.numSamples; | |||
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, uint32 timeout) | |||
{ | |||
if (!source || source->getTotalLength() <= 0) | |||
if (source == nullptr || source->getTotalLength() <= 0) | |||
return false; | |||
if (nextPlayPos + info.numSamples < 0) | |||
return true; | |||
if (! isLooping() && nextPlayPos > getTotalLength()) | |||
if ((nextPlayPos + info.numSamples < 0) | |||
|| (! isLooping() && nextPlayPos > getTotalLength())) | |||
return true; | |||
auto now = Time::getMillisecondCounter(); | |||
auto startTime = now; | |||
const auto startTime = Time::getMillisecondCounter(); | |||
auto now = startTime; | |||
auto elapsed = (now >= startTime ? now - startTime | |||
: (std::numeric_limits<uint32>::max() - startTime) + now); | |||
while (elapsed <= timeout) | |||
{ | |||
{ | |||
const ScopedLock sl (bufferStartPosLock); | |||
auto start = bufferValidStart.load(); | |||
auto end = bufferValidEnd.load(); | |||
auto pos = nextPlayPos.load(); | |||
const auto bufferRange = getValidBufferRange (info.numSamples); | |||
auto validStart = static_cast<int> (jlimit (start, end, pos) - pos); | |||
auto validEnd = static_cast<int> (jlimit (start, end, pos + info.numSamples) - pos); | |||
const auto validStart = bufferRange.getStart(); | |||
const auto validEnd = bufferRange.getEnd(); | |||
if (validStart <= 0 && validStart < validEnd && validEnd >= info.numSamples) | |||
return true; | |||
if (validStart <= 0 | |||
&& validStart < validEnd | |||
&& validEnd >= info.numSamples) | |||
{ | |||
return true; | |||
} | |||
if (elapsed < timeout && (! bufferReadyEvent.wait (static_cast<int> (timeout - elapsed)))) | |||
if (elapsed < timeout | |||
&& ! bufferReadyEvent.wait (static_cast<int> (timeout - elapsed))) | |||
{ | |||
return false; | |||
} | |||
now = Time::getMillisecondCounter(); | |||
elapsed = (now >= startTime ? now - startTime | |||
@@ -202,7 +203,7 @@ bool BufferingAudioSource::waitForNextAudioBlockReady (const AudioSourceChannelI | |||
int64 BufferingAudioSource::getNextReadPosition() const | |||
{ | |||
jassert (source->getTotalLength() > 0); | |||
auto pos = nextPlayPos.load(); | |||
const auto pos = nextPlayPos.load(); | |||
return (source->isLooping() && nextPlayPos > 0) | |||
? pos % source->getTotalLength() | |||
@@ -211,18 +212,28 @@ int64 BufferingAudioSource::getNextReadPosition() const | |||
void BufferingAudioSource::setNextReadPosition (int64 newPosition) | |||
{ | |||
const ScopedLock sl (bufferStartPosLock); | |||
const ScopedLock sl (bufferRangeLock); | |||
nextPlayPos = newPosition; | |||
backgroundThread.moveToFrontOfQueue (this); | |||
} | |||
Range<int> BufferingAudioSource::getValidBufferRange (int numSamples) const | |||
{ | |||
const ScopedLock sl (bufferRangeLock); | |||
const auto pos = nextPlayPos.load(); | |||
return { (int) (jlimit (bufferValidStart, bufferValidEnd, pos) - pos), | |||
(int) (jlimit (bufferValidStart, bufferValidEnd, pos + numSamples) - pos) }; | |||
} | |||
bool BufferingAudioSource::readNextBufferChunk() | |||
{ | |||
int64 newBVS, newBVE, sectionToReadStart, sectionToReadEnd; | |||
{ | |||
const ScopedLock sl (bufferStartPosLock); | |||
const ScopedLock sl (bufferRangeLock); | |||
if (wasSourceLooping != isLooping()) | |||
{ | |||
@@ -236,7 +247,7 @@ bool BufferingAudioSource::readNextBufferChunk() | |||
sectionToReadStart = 0; | |||
sectionToReadEnd = 0; | |||
const int maxChunkSize = 2048; | |||
constexpr int maxChunkSize = 2048; | |||
if (newBVS < bufferValidStart || newBVS >= bufferValidEnd) | |||
{ | |||
@@ -257,7 +268,7 @@ bool BufferingAudioSource::readNextBufferChunk() | |||
sectionToReadEnd = newBVE; | |||
bufferValidStart = newBVS; | |||
bufferValidEnd = jmin (bufferValidEnd.load(), newBVE); | |||
bufferValidEnd = jmin (bufferValidEnd, newBVE); | |||
} | |||
} | |||
@@ -265,8 +276,9 @@ bool BufferingAudioSource::readNextBufferChunk() | |||
return false; | |||
jassert (buffer.getNumSamples() > 0); | |||
auto bufferIndexStart = (int) (sectionToReadStart % buffer.getNumSamples()); | |||
auto bufferIndexEnd = (int) (sectionToReadEnd % buffer.getNumSamples()); | |||
const auto bufferIndexStart = (int) (sectionToReadStart % buffer.getNumSamples()); | |||
const auto bufferIndexEnd = (int) (sectionToReadEnd % buffer.getNumSamples()); | |||
if (bufferIndexStart < bufferIndexEnd) | |||
{ | |||
@@ -276,7 +288,7 @@ bool BufferingAudioSource::readNextBufferChunk() | |||
} | |||
else | |||
{ | |||
auto initialSize = buffer.getNumSamples() - bufferIndexStart; | |||
const auto initialSize = buffer.getNumSamples() - bufferIndexStart; | |||
readBufferSection (sectionToReadStart, | |||
initialSize, | |||
@@ -288,7 +300,7 @@ bool BufferingAudioSource::readNextBufferChunk() | |||
} | |||
{ | |||
const ScopedLock sl2 (bufferStartPosLock); | |||
const ScopedLock sl2 (bufferRangeLock); | |||
bufferValidStart = newBVS; | |||
bufferValidEnd = newBVE; | |||
@@ -304,6 +316,8 @@ void BufferingAudioSource::readBufferSection (int64 start, int length, int buffe | |||
source->setNextReadPosition (start); | |||
AudioSourceChannelInfo info (&buffer, bufferOffset, length); | |||
const ScopedLock sl (callbackLock); | |||
source->getNextAudioBlock (info); | |||
} | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -98,21 +98,26 @@ public: | |||
bool waitForNextAudioBlockReady (const AudioSourceChannelInfo& info, const uint32 timeout); | |||
private: | |||
//============================================================================== | |||
Range<int> getValidBufferRange (int numSamples) const; | |||
bool readNextBufferChunk(); | |||
void readBufferSection (int64 start, int length, int bufferOffset); | |||
int useTimeSlice() override; | |||
//============================================================================== | |||
OptionalScopedPointer<PositionableAudioSource> source; | |||
TimeSliceThread& backgroundThread; | |||
int numberOfSamplesToBuffer, numberOfChannels; | |||
AudioBuffer<float> buffer; | |||
CriticalSection bufferStartPosLock; | |||
CriticalSection callbackLock, bufferRangeLock; | |||
WaitableEvent bufferReadyEvent; | |||
std::atomic<int64> bufferValidStart { 0 }, bufferValidEnd { 0 }, nextPlayPos { 0 }; | |||
int64 bufferValidStart = 0, bufferValidEnd = 0; | |||
std::atomic<int64> nextPlayPos { 0 }; | |||
double sampleRate = 0; | |||
bool wasSourceLooping = false, isPrepared = false, prefillBuffer; | |||
bool readNextBufferChunk(); | |||
void readBufferSection (int64 start, int length, int bufferOffset); | |||
int useTimeSlice() override; | |||
bool wasSourceLooping = false, isPrepared = false; | |||
const bool prefillBuffer; | |||
//============================================================================== | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BufferingAudioSource) | |||
}; | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -631,14 +631,6 @@ private: | |||
template <typename floatType> | |||
void processNextBlock (AudioBuffer<floatType>&, const MidiBuffer&, int startSample, int numSamples); | |||
#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) | |||
}; | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -31,16 +31,19 @@ namespace juce | |||
with setParameters() then call getNextSample() to get the envelope value to be applied | |||
to each audio sample or applyEnvelopeToBuffer() to apply the envelope to a whole buffer. | |||
Do not change the parameters during playback. If you change the parameters before the | |||
release stage has completed then you must call reset() before the next call to | |||
noteOn(). | |||
@tags{Audio} | |||
*/ | |||
class ADSR | |||
class JUCE_API ADSR | |||
{ | |||
public: | |||
//============================================================================== | |||
ADSR() | |||
{ | |||
setSampleRate (44100.0); | |||
setParameters ({}); | |||
recalculateRates(); | |||
} | |||
//============================================================================== | |||
@@ -49,19 +52,22 @@ public: | |||
@tags{Audio} | |||
*/ | |||
struct Parameters | |||
struct JUCE_API Parameters | |||
{ | |||
/** Attack time in seconds. */ | |||
float attack = 0.1f; | |||
/** Decay time in seconds. */ | |||
float decay = 0.1f; | |||
/** Sustain level. */ | |||
float sustain = 1.0f; | |||
Parameters() = default; | |||
Parameters (float attackTimeSeconds, | |||
float decayTimeSeconds, | |||
float sustainLevel, | |||
float releaseTimeSeconds) | |||
: attack (attackTimeSeconds), | |||
decay (decayTimeSeconds), | |||
sustain (sustainLevel), | |||
release (releaseTimeSeconds) | |||
{ | |||
} | |||
/** Release time in seconds. */ | |||
float release = 0.1f; | |||
float attack = 0.1f, decay = 0.1f, sustain = 1.0f, release = 0.1f; | |||
}; | |||
/** Sets the parameters that will be used by an ADSR object. | |||
@@ -73,70 +79,69 @@ public: | |||
*/ | |||
void setParameters (const Parameters& newParameters) | |||
{ | |||
currentParameters = newParameters; | |||
sustainLevel = newParameters.sustain; | |||
calculateRates (newParameters); | |||
// need to call setSampleRate() first! | |||
jassert (sampleRate > 0.0); | |||
if (currentState != State::idle) | |||
checkCurrentState(); | |||
parameters = newParameters; | |||
recalculateRates(); | |||
} | |||
/** Returns the parameters currently being used by an ADSR object. | |||
@see setParameters | |||
*/ | |||
const Parameters& getParameters() const { return currentParameters; } | |||
const Parameters& getParameters() const noexcept { return parameters; } | |||
/** Returns true if the envelope is in its attack, decay, sustain or release stage. */ | |||
bool isActive() const noexcept { return currentState != State::idle; } | |||
bool isActive() const noexcept { return state != State::idle; } | |||
//============================================================================== | |||
/** Sets the sample rate that will be used for the envelope. | |||
This must be called before the getNextSample() or setParameters() methods. | |||
*/ | |||
void setSampleRate (double sampleRate) | |||
void setSampleRate (double newSampleRate) noexcept | |||
{ | |||
jassert (sampleRate > 0.0); | |||
sr = sampleRate; | |||
jassert (newSampleRate > 0.0); | |||
sampleRate = newSampleRate; | |||
} | |||
//============================================================================== | |||
/** Resets the envelope to an idle state. */ | |||
void reset() | |||
void reset() noexcept | |||
{ | |||
envelopeVal = 0.0f; | |||
currentState = State::idle; | |||
state = State::idle; | |||
} | |||
/** Starts the attack phase of the envelope. */ | |||
void noteOn() | |||
void noteOn() noexcept | |||
{ | |||
if (attackRate > 0.0f) | |||
{ | |||
currentState = State::attack; | |||
state = State::attack; | |||
} | |||
else if (decayRate > 0.0f) | |||
{ | |||
envelopeVal = 1.0f; | |||
currentState = State::decay; | |||
state = State::decay; | |||
} | |||
else | |||
{ | |||
currentState = State::sustain; | |||
envelopeVal = parameters.sustain; | |||
state = State::sustain; | |||
} | |||
} | |||
/** Starts the release phase of the envelope. */ | |||
void noteOff() | |||
void noteOff() noexcept | |||
{ | |||
if (currentState != State::idle) | |||
if (state != State::idle) | |||
{ | |||
if (currentParameters.release > 0.0f) | |||
if (parameters.release > 0.0f) | |||
{ | |||
releaseRate = static_cast<float> (envelopeVal / (currentParameters.release * sr)); | |||
currentState = State::release; | |||
releaseRate = (float) (envelopeVal / (parameters.release * sampleRate)); | |||
state = State::release; | |||
} | |||
else | |||
{ | |||
@@ -150,45 +155,56 @@ public: | |||
@see applyEnvelopeToBuffer | |||
*/ | |||
float getNextSample() | |||
float getNextSample() noexcept | |||
{ | |||
if (currentState == State::idle) | |||
return 0.0f; | |||
if (currentState == State::attack) | |||
switch (state) | |||
{ | |||
envelopeVal += attackRate; | |||
case State::idle: | |||
{ | |||
return 0.0f; | |||
} | |||
if (envelopeVal >= 1.0f) | |||
case State::attack: | |||
{ | |||
envelopeVal = 1.0f; | |||
envelopeVal += attackRate; | |||
if (decayRate > 0.0f) | |||
currentState = State::decay; | |||
else | |||
currentState = State::sustain; | |||
if (envelopeVal >= 1.0f) | |||
{ | |||
envelopeVal = 1.0f; | |||
goToNextState(); | |||
} | |||
break; | |||
} | |||
} | |||
else if (currentState == State::decay) | |||
{ | |||
envelopeVal -= decayRate; | |||
if (envelopeVal <= sustainLevel) | |||
case State::decay: | |||
{ | |||
envelopeVal = sustainLevel; | |||
currentState = State::sustain; | |||
envelopeVal -= decayRate; | |||
if (envelopeVal <= parameters.sustain) | |||
{ | |||
envelopeVal = parameters.sustain; | |||
goToNextState(); | |||
} | |||
break; | |||
} | |||
} | |||
else if (currentState == State::sustain) | |||
{ | |||
envelopeVal = sustainLevel; | |||
} | |||
else if (currentState == State::release) | |||
{ | |||
envelopeVal -= releaseRate; | |||
if (envelopeVal <= 0.0f) | |||
reset(); | |||
case State::sustain: | |||
{ | |||
envelopeVal = parameters.sustain; | |||
break; | |||
} | |||
case State::release: | |||
{ | |||
envelopeVal -= releaseRate; | |||
if (envelopeVal <= 0.0f) | |||
goToNextState(); | |||
break; | |||
} | |||
} | |||
return envelopeVal; | |||
@@ -204,6 +220,18 @@ public: | |||
{ | |||
jassert (startSample + numSamples <= buffer.getNumSamples()); | |||
if (state == State::idle) | |||
{ | |||
buffer.clear (startSample, numSamples); | |||
return; | |||
} | |||
if (state == State::sustain) | |||
{ | |||
buffer.applyGain (startSample, numSamples, parameters.sustain); | |||
return; | |||
} | |||
auto numChannels = buffer.getNumChannels(); | |||
while (--numSamples >= 0) | |||
@@ -219,30 +247,51 @@ public: | |||
private: | |||
//============================================================================== | |||
void calculateRates (const Parameters& parameters) | |||
void recalculateRates() noexcept | |||
{ | |||
// need to call setSampleRate() first! | |||
jassert (sr > 0.0); | |||
auto getRate = [] (float distance, float timeInSeconds, double sr) | |||
{ | |||
return timeInSeconds > 0.0f ? (float) (distance / (timeInSeconds * sr)) : -1.0f; | |||
}; | |||
attackRate = getRate (1.0f, parameters.attack, sampleRate); | |||
decayRate = getRate (1.0f - parameters.sustain, parameters.decay, sampleRate); | |||
releaseRate = getRate (parameters.sustain, parameters.release, sampleRate); | |||
attackRate = (parameters.attack > 0.0f ? static_cast<float> (1.0f / (parameters.attack * sr)) : -1.0f); | |||
decayRate = (parameters.decay > 0.0f ? static_cast<float> ((1.0f - sustainLevel) / (parameters.decay * sr)) : -1.0f); | |||
if ((state == State::attack && attackRate <= 0.0f) | |||
|| (state == State::decay && (decayRate <= 0.0f || envelopeVal <= parameters.sustain)) | |||
|| (state == State::release && releaseRate <= 0.0f)) | |||
{ | |||
goToNextState(); | |||
} | |||
} | |||
void checkCurrentState() | |||
void goToNextState() noexcept | |||
{ | |||
if (currentState == State::attack && attackRate <= 0.0f) currentState = decayRate > 0.0f ? State::decay : State::sustain; | |||
else if (currentState == State::decay && decayRate <= 0.0f) currentState = State::sustain; | |||
else if (currentState == State::release && releaseRate <= 0.0f) reset(); | |||
if (state == State::attack) | |||
{ | |||
state = (decayRate > 0.0f ? State::decay : State::sustain); | |||
return; | |||
} | |||
if (state == State::decay) | |||
{ | |||
state = State::sustain; | |||
return; | |||
} | |||
if (state == State::release) | |||
reset(); | |||
} | |||
//============================================================================== | |||
enum class State { idle, attack, decay, sustain, release }; | |||
State currentState = State::idle; | |||
Parameters currentParameters; | |||
State state = State::idle; | |||
Parameters parameters; | |||
double sr = 0.0; | |||
float envelopeVal = 0.0f, sustainLevel = 0.0f, attackRate = 0.0f, decayRate = 0.0f, releaseRate = 0.0f; | |||
double sampleRate = 44100.0; | |||
float envelopeVal = 0.0f, attackRate = 0.0f, decayRate = 0.0f, releaseRate = 0.0f; | |||
}; | |||
} // namespace juce |
@@ -0,0 +1,257 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
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 ADSRTests : public UnitTest | |||
{ | |||
ADSRTests() : UnitTest ("ADSR", UnitTestCategories::audio) {} | |||
void runTest() override | |||
{ | |||
constexpr double sampleRate = 44100.0; | |||
const ADSR::Parameters parameters { 0.1f, 0.1f, 0.5f, 0.1f }; | |||
ADSR adsr; | |||
adsr.setSampleRate (sampleRate); | |||
adsr.setParameters (parameters); | |||
beginTest ("Idle"); | |||
{ | |||
adsr.reset(); | |||
expect (! adsr.isActive()); | |||
expectEquals (adsr.getNextSample(), 0.0f); | |||
} | |||
beginTest ("Attack"); | |||
{ | |||
adsr.reset(); | |||
adsr.noteOn(); | |||
expect (adsr.isActive()); | |||
auto buffer = getTestBuffer (sampleRate, parameters.attack); | |||
adsr.applyEnvelopeToBuffer (buffer, 0, buffer.getNumSamples()); | |||
expect (isIncreasing (buffer)); | |||
} | |||
beginTest ("Decay"); | |||
{ | |||
adsr.reset(); | |||
adsr.noteOn(); | |||
advanceADSR (adsr, roundToInt (parameters.attack * sampleRate)); | |||
auto buffer = getTestBuffer (sampleRate, parameters.decay); | |||
adsr.applyEnvelopeToBuffer (buffer, 0, buffer.getNumSamples()); | |||
expect (isDecreasing (buffer)); | |||
} | |||
beginTest ("Sustain"); | |||
{ | |||
adsr.reset(); | |||
adsr.noteOn(); | |||
advanceADSR (adsr, roundToInt ((parameters.attack + parameters.decay + 0.01) * sampleRate)); | |||
auto random = getRandom(); | |||
for (int numTests = 0; numTests < 100; ++numTests) | |||
{ | |||
const auto sustainLevel = random.nextFloat(); | |||
const auto sustainLength = jmax (0.1f, random.nextFloat()); | |||
adsr.setParameters ({ parameters.attack, parameters.decay, sustainLevel, parameters.release }); | |||
auto buffer = getTestBuffer (sampleRate, sustainLength); | |||
adsr.applyEnvelopeToBuffer (buffer, 0, buffer.getNumSamples()); | |||
expect (isSustained (buffer, sustainLevel)); | |||
} | |||
} | |||
beginTest ("Release"); | |||
{ | |||
adsr.reset(); | |||
adsr.noteOn(); | |||
advanceADSR (adsr, roundToInt ((parameters.attack + parameters.decay) * sampleRate)); | |||
adsr.noteOff(); | |||
auto buffer = getTestBuffer (sampleRate, parameters.release); | |||
adsr.applyEnvelopeToBuffer (buffer, 0, buffer.getNumSamples()); | |||
expect (isDecreasing (buffer)); | |||
} | |||
beginTest ("Zero-length attack jumps to decay"); | |||
{ | |||
adsr.reset(); | |||
adsr.setParameters ({ 0.0f, parameters.decay, parameters.sustain, parameters.release }); | |||
adsr.noteOn(); | |||
auto buffer = getTestBuffer (sampleRate, parameters.decay); | |||
adsr.applyEnvelopeToBuffer (buffer, 0, buffer.getNumSamples()); | |||
expect (isDecreasing (buffer)); | |||
} | |||
beginTest ("Zero-length decay jumps to sustain"); | |||
{ | |||
adsr.reset(); | |||
adsr.setParameters ({ parameters.attack, 0.0f, parameters.sustain, parameters.release }); | |||
adsr.noteOn(); | |||
advanceADSR (adsr, roundToInt (parameters.attack * sampleRate)); | |||
adsr.getNextSample(); | |||
expectEquals (adsr.getNextSample(), parameters.sustain); | |||
auto buffer = getTestBuffer (sampleRate, 1); | |||
adsr.applyEnvelopeToBuffer (buffer, 0, buffer.getNumSamples()); | |||
expect (isSustained (buffer, parameters.sustain)); | |||
} | |||
beginTest ("Zero-length attack and decay jumps to sustain"); | |||
{ | |||
adsr.reset(); | |||
adsr.setParameters ({ 0.0f, 0.0f, parameters.sustain, parameters.release }); | |||
adsr.noteOn(); | |||
expectEquals (adsr.getNextSample(), parameters.sustain); | |||
auto buffer = getTestBuffer (sampleRate, 1); | |||
adsr.applyEnvelopeToBuffer (buffer, 0, buffer.getNumSamples()); | |||
expect (isSustained (buffer, parameters.sustain)); | |||
} | |||
beginTest ("Zero-length attack and decay releases correctly"); | |||
{ | |||
adsr.reset(); | |||
adsr.setParameters ({ 0.0f, 0.0f, parameters.sustain, parameters.release }); | |||
adsr.noteOn(); | |||
adsr.noteOff(); | |||
auto buffer = getTestBuffer (sampleRate, parameters.release); | |||
adsr.applyEnvelopeToBuffer (buffer, 0, buffer.getNumSamples()); | |||
expect (isDecreasing (buffer)); | |||
} | |||
beginTest ("Zero-length release resets to idle"); | |||
{ | |||
adsr.reset(); | |||
adsr.setParameters ({ parameters.attack, parameters.decay, parameters.sustain, 0.0f }); | |||
adsr.noteOn(); | |||
advanceADSR (adsr, roundToInt ((parameters.attack + parameters.decay) * sampleRate)); | |||
adsr.noteOff(); | |||
expect (! adsr.isActive()); | |||
} | |||
} | |||
static void advanceADSR (ADSR& adsr, int numSamplesToAdvance) | |||
{ | |||
while (--numSamplesToAdvance >= 0) | |||
adsr.getNextSample(); | |||
} | |||
static AudioBuffer<float> getTestBuffer (double sampleRate, float lengthInSeconds) | |||
{ | |||
AudioBuffer<float> buffer { 2, roundToInt (lengthInSeconds * sampleRate) }; | |||
for (int channel = 0; channel < buffer.getNumChannels(); ++channel) | |||
for (int sample = 0; sample < buffer.getNumSamples(); ++sample) | |||
buffer.setSample (channel, sample, 1.0f); | |||
return buffer; | |||
} | |||
static bool isIncreasing (const AudioBuffer<float>& b) | |||
{ | |||
jassert (b.getNumChannels() > 0 && b.getNumSamples() > 0); | |||
for (int channel = 0; channel < b.getNumChannels(); ++channel) | |||
{ | |||
float previousSample = -1.0f; | |||
for (int sample = 0; sample < b.getNumSamples(); ++sample) | |||
{ | |||
const auto currentSample = b.getSample (channel, sample); | |||
if (currentSample <= previousSample) | |||
return false; | |||
previousSample = currentSample; | |||
} | |||
} | |||
return true; | |||
} | |||
static bool isDecreasing (const AudioBuffer<float>& b) | |||
{ | |||
jassert (b.getNumChannels() > 0 && b.getNumSamples() > 0); | |||
for (int channel = 0; channel < b.getNumChannels(); ++channel) | |||
{ | |||
float previousSample = std::numeric_limits<float>::max(); | |||
for (int sample = 0; sample < b.getNumSamples(); ++sample) | |||
{ | |||
const auto currentSample = b.getSample (channel, sample); | |||
if (currentSample >= previousSample) | |||
return false; | |||
previousSample = currentSample; | |||
} | |||
} | |||
return true; | |||
} | |||
static bool isSustained (const AudioBuffer<float>& b, float sustainLevel) | |||
{ | |||
jassert (b.getNumChannels() > 0 && b.getNumSamples() > 0); | |||
for (int channel = 0; channel < b.getNumChannels(); ++channel) | |||
if (b.findMinMax (channel, 0, b.getNumSamples()) != Range<float> { sustainLevel, sustainLevel }) | |||
return false; | |||
return true; | |||
} | |||
}; | |||
static ADSRTests adsrTests; | |||
} // namespace juce |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,19 +2,16 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||
End User License Agreement: www.juce.com/juce-6-licence | |||
Privacy Policy: www.juce.com/juce-privacy-policy | |||
Or: You may also use this code under the terms of the GPL v3 (see | |||
www.gnu.org/licenses). | |||
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 | |||
@@ -415,7 +412,7 @@ private: | |||
pos -= 1.0; | |||
} | |||
*output++ += gain * InterpolatorTraits::valueAtOffset ((float) pos); | |||
*output++ += gain * InterpolatorTraits::valueAtOffset (lastInputSamples, (float) pos, indexBuffer); | |||
pos += speedRatio; | |||
} | |||
} | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -258,42 +258,42 @@ IIRCoefficients IIRCoefficients::makePeakFilter (double sampleRate, | |||
} | |||
//============================================================================== | |||
IIRFilter::IIRFilter() noexcept | |||
{ | |||
} | |||
template <typename Mutex> | |||
IIRFilterBase<Mutex>::IIRFilterBase() noexcept = default; | |||
IIRFilter::IIRFilter (const IIRFilter& other) noexcept : active (other.active) | |||
template <typename Mutex> | |||
IIRFilterBase<Mutex>::IIRFilterBase (const IIRFilterBase& other) noexcept : active (other.active) | |||
{ | |||
const SpinLock::ScopedLockType sl (other.processLock); | |||
const typename Mutex::ScopedLockType sl (other.processLock); | |||
coefficients = other.coefficients; | |||
} | |||
IIRFilter::~IIRFilter() noexcept | |||
{ | |||
} | |||
//============================================================================== | |||
void IIRFilter::makeInactive() noexcept | |||
template <typename Mutex> | |||
void IIRFilterBase<Mutex>::makeInactive() noexcept | |||
{ | |||
const SpinLock::ScopedLockType sl (processLock); | |||
const typename Mutex::ScopedLockType sl (processLock); | |||
active = false; | |||
} | |||
void IIRFilter::setCoefficients (const IIRCoefficients& newCoefficients) noexcept | |||
template <typename Mutex> | |||
void IIRFilterBase<Mutex>::setCoefficients (const IIRCoefficients& newCoefficients) noexcept | |||
{ | |||
const SpinLock::ScopedLockType sl (processLock); | |||
const typename Mutex::ScopedLockType sl (processLock); | |||
coefficients = newCoefficients; | |||
active = true; | |||
} | |||
//============================================================================== | |||
void IIRFilter::reset() noexcept | |||
template <typename Mutex> | |||
void IIRFilterBase<Mutex>::reset() noexcept | |||
{ | |||
const SpinLock::ScopedLockType sl (processLock); | |||
const typename Mutex::ScopedLockType sl (processLock); | |||
v1 = v2 = 0.0; | |||
} | |||
float IIRFilter::processSingleSampleRaw (float in) noexcept | |||
template <typename Mutex> | |||
float IIRFilterBase<Mutex>::processSingleSampleRaw (float in) noexcept | |||
{ | |||
auto out = coefficients.coefficients[0] * in + v1; | |||
@@ -305,9 +305,10 @@ float IIRFilter::processSingleSampleRaw (float in) noexcept | |||
return out; | |||
} | |||
void IIRFilter::processSamples (float* const samples, const int numSamples) noexcept | |||
template <typename Mutex> | |||
void IIRFilterBase<Mutex>::processSamples (float* const samples, const int numSamples) noexcept | |||
{ | |||
const SpinLock::ScopedLockType sl (processLock); | |||
const typename Mutex::ScopedLockType sl (processLock); | |||
if (active) | |||
{ | |||
@@ -333,4 +334,7 @@ void IIRFilter::processSamples (float* const samples, const int numSamples) noex | |||
} | |||
} | |||
template class IIRFilterBase<SpinLock>; | |||
template class IIRFilterBase<DummyCriticalSection>; | |||
} // namespace juce |
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -153,7 +153,8 @@ public: | |||
@tags{Audio} | |||
*/ | |||
class JUCE_API IIRFilter | |||
template <typename Mutex> | |||
class JUCE_API IIRFilterBase | |||
{ | |||
public: | |||
//============================================================================== | |||
@@ -163,13 +164,10 @@ public: | |||
you process with it. Use the setCoefficients() method to turn it into the | |||
type of filter needed. | |||
*/ | |||
IIRFilter() noexcept; | |||
IIRFilterBase() noexcept; | |||
/** Creates a copy of another filter. */ | |||
IIRFilter (const IIRFilter&) noexcept; | |||
/** Destructor. */ | |||
~IIRFilter() noexcept; | |||
IIRFilterBase (const IIRFilterBase&) noexcept; | |||
//============================================================================== | |||
/** Clears the filter so that any incoming data passes through unchanged. */ | |||
@@ -202,7 +200,7 @@ public: | |||
protected: | |||
//============================================================================== | |||
SpinLock processLock; | |||
Mutex processLock; | |||
IIRCoefficients coefficients; | |||
float v1 = 0, v2 = 0; | |||
bool active = false; | |||
@@ -214,4 +212,43 @@ protected: | |||
JUCE_LEAK_DETECTOR (IIRFilter) | |||
}; | |||
/** | |||
An IIR filter that can perform low, high, or band-pass filtering on an | |||
audio signal, and which attempts to implement basic thread-safety. | |||
This class synchronises calls to some of its member functions, making it | |||
safe (although not necessarily real-time-safe) to reset the filter or | |||
apply new coefficients while the filter is processing on another thread. | |||
In most cases this style of internal locking should not be used, and you | |||
should attempt to provide thread-safety at a higher level in your program. | |||
If you can guarantee that calls to the filter will be synchronised externally, | |||
you could consider switching to SingleThreadedIIRFilter instead. | |||
@see SingleThreadedIIRFilter, IIRCoefficient, IIRFilterAudioSource | |||
@tags{Audio} | |||
*/ | |||
class IIRFilter : public IIRFilterBase<SpinLock> | |||
{ | |||
public: | |||
using IIRFilterBase::IIRFilterBase; | |||
}; | |||
/** | |||
An IIR filter that can perform low, high, or band-pass filtering on an | |||
audio signal, with no thread-safety guarantees. | |||
You should use this class if you need an IIR filter, and don't plan to | |||
call its member functions from multiple threads at once. | |||
@see IIRFilter, IIRCoefficient, IIRFilterAudioSource | |||
@tags{Audio} | |||
*/ | |||
class SingleThreadedIIRFilter : public IIRFilterBase<DummyCriticalSection> | |||
{ | |||
public: | |||
using IIRFilterBase::IIRFilterBase; | |||
}; | |||
} // namespace juce |
@@ -2,19 +2,16 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||
End User License Agreement: www.juce.com/juce-6-licence | |||
Privacy Policy: www.juce.com/juce-privacy-policy | |||
Or: You may also use this code under the terms of the GPL v3 (see | |||
www.gnu.org/licenses). | |||
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 | |||
@@ -2,19 +2,16 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||
End User License Agreement: www.juce.com/juce-6-licence | |||
Privacy Policy: www.juce.com/juce-privacy-policy | |||
Or: You may also use this code under the terms of the GPL v3 (see | |||
www.gnu.org/licenses). | |||
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 | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -131,6 +131,7 @@ public: | |||
/** Applies the reverb to two stereo channels of audio data. */ | |||
void processStereo (float* const left, float* const right, const int numSamples) noexcept | |||
{ | |||
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6011) | |||
jassert (left != nullptr && right != nullptr); | |||
for (int i = 0; i < numSamples; ++i) | |||
@@ -160,11 +161,13 @@ public: | |||
left[i] = outL * wet1 + outR * wet2 + left[i] * dry; | |||
right[i] = outR * wet1 + outL * wet2 + right[i] * dry; | |||
} | |||
JUCE_END_IGNORE_WARNINGS_MSVC | |||
} | |||
/** Applies the reverb to a single mono channel of audio data. */ | |||
void processMono (float* const samples, const int numSamples) noexcept | |||
{ | |||
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6011) | |||
jassert (samples != nullptr); | |||
for (int i = 0; i < numSamples; ++i) | |||
@@ -186,6 +189,7 @@ public: | |||
samples[i] = output * wet1 + samples[i] * dry; | |||
} | |||
JUCE_END_IGNORE_WARNINGS_MSVC | |||
} | |||
private: | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -2,7 +2,7 @@ | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
Copyright (c) 2022 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
@@ -58,8 +58,6 @@ public: | |||
/** Constructor. */ | |||
SmoothedValueBase() = default; | |||
virtual ~SmoothedValueBase() {} | |||
//============================================================================== | |||
/** Returns true if the current value is currently being interpolated. */ | |||
bool isSmoothing() const noexcept { return countdown > 0; } | |||
@@ -330,9 +328,8 @@ public: | |||
} | |||
//============================================================================== | |||
/** THIS FUNCTION IS DEPRECATED. | |||
Use `setTargetValue (float)` and `setCurrentAndTargetValue()` instead: | |||
#ifndef DOXYGEN | |||
/** Using the new methods: | |||
lsv.setValue (x, false); -> lsv.setTargetValue (x); | |||
lsv.setValue (x, true); -> lsv.setCurrentAndTargetValue (x); | |||
@@ -340,7 +337,8 @@ public: | |||
@param newValue The new target value | |||
@param force If true, the value will be set immediately, bypassing the ramp | |||
*/ | |||
JUCE_DEPRECATED_WITH_BODY (void setValue (FloatType newValue, bool force = false) noexcept, | |||
[[deprecated ("Use setTargetValue and setCurrentAndTargetValue instead.")]] | |||
void setValue (FloatType newValue, bool force = false) noexcept | |||
{ | |||
if (force) | |||
{ | |||
@@ -349,7 +347,8 @@ public: | |||
} | |||
setTargetValue (newValue); | |||
}) | |||
} | |||
#endif | |||
private: | |||
//============================================================================== | |||
@@ -510,7 +509,7 @@ public: | |||
expect (referenceData.getSample (0, 10) < sv.getTargetValue()); | |||
expectWithinAbsoluteError (referenceData.getSample (0, 11), | |||
sv.getTargetValue(), | |||
1.0e-7f); | |||
2.0e-7f); | |||
auto getUnitData = [] (int numSamplesToGenerate) | |||
{ | |||
@@ -528,7 +527,7 @@ public: | |||
for (int i = 0; i < test.getNumSamples(); ++i) | |||
expectWithinAbsoluteError (test.getSample (0, i), | |||
reference.getSample (0, i), | |||
1.0e-7f); | |||
2.0e-7f); | |||
}; | |||
auto testData = getUnitData (numSamples); | |||