@@ -32,6 +32,7 @@ SynthesiserVoice::SynthesiserVoice() | |||
currentPlayingMidiChannel (0), | |||
noteOnTime (0), | |||
keyIsDown (false), | |||
sustainPedalDown (false), | |||
sostenutoPedalDown (false) | |||
{ | |||
} | |||
@@ -296,6 +297,7 @@ void Synthesiser::startVoice (SynthesiserVoice* const voice, | |||
voice->currentlyPlayingSound = sound; | |||
voice->keyIsDown = true; | |||
voice->sostenutoPedalDown = false; | |||
voice->sustainPedalDown = sustainPedalsDown[midiChannel]; | |||
voice->startNote (midiNoteNumber, velocity, sound, | |||
lastPitchWheelValues [midiChannel - 1]); | |||
@@ -331,9 +333,11 @@ void Synthesiser::noteOff (const int midiChannel, | |||
if (sound->appliesToNote (midiNoteNumber) | |||
&& sound->appliesToChannel (midiChannel)) | |||
{ | |||
jassert (! voice->keyIsDown || voice->sustainPedalDown == sustainPedalsDown [midiChannel]); | |||
voice->keyIsDown = false; | |||
if (! (sustainPedalsDown [midiChannel] || voice->sostenutoPedalDown)) | |||
if (! (voice->sustainPedalDown || voice->sostenutoPedalDown)) | |||
stopVoice (voice, velocity, allowTailOff); | |||
} | |||
} | |||
@@ -427,6 +431,14 @@ void Synthesiser::handleSustainPedal (int midiChannel, bool isDown) | |||
if (isDown) | |||
{ | |||
sustainPedalsDown.setBit (midiChannel); | |||
for (int i = voices.size(); --i >= 0;) | |||
{ | |||
SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
if (voice->isPlayingChannel (midiChannel) && voice->isKeyDown()) | |||
voice->sustainPedalDown = true; | |||
} | |||
} | |||
else | |||
{ | |||
@@ -434,8 +446,13 @@ void Synthesiser::handleSustainPedal (int midiChannel, bool isDown) | |||
{ | |||
SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
if (voice->isPlayingChannel (midiChannel) && ! voice->keyIsDown) | |||
stopVoice (voice, 1.0f, true); | |||
if (voice->isPlayingChannel (midiChannel)) | |||
{ | |||
voice->sustainPedalDown = false; | |||
if (! voice->isKeyDown()) | |||
stopVoice (voice, 1.0f, true); | |||
} | |||
} | |||
sustainPedalsDown.clearBit (midiChannel); | |||
@@ -505,8 +522,13 @@ struct VoiceAgeSorter | |||
SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, | |||
int /*midiChannel*/, int midiNoteNumber) const | |||
{ | |||
SynthesiserVoice* bottom = nullptr; | |||
SynthesiserVoice* top = nullptr; | |||
// This voice-stealing algorithm applies the following heuristics: | |||
// - Re-use the oldest notes first | |||
// - Protect the lowest & topmost notes, even if sustained, but not if they've been released. | |||
// These are the voices we want to protect (ie: only steal if unavoidable) | |||
SynthesiserVoice* low = nullptr; // Lowest sounding note, might be sustained, but NOT in release phase | |||
SynthesiserVoice* top = nullptr; // Highest sounding note, might be sustained, but NOT in release phase | |||
// this is a list of voices we can steal, sorted by how long they've been running | |||
Array<SynthesiserVoice*> usableVoices; | |||
@@ -516,24 +538,33 @@ SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, | |||
{ | |||
SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
jassert (voice->isVoiceActive()); // We wouldn't be here otherwise | |||
if (voice->canPlaySound (soundToPlay)) | |||
{ | |||
VoiceAgeSorter sorter; | |||
usableVoices.addSorted (sorter, voice); | |||
const int note = voice->getCurrentlyPlayingNote(); | |||
if (! voice->isPlayingButReleased()) // Don't protect released notes | |||
{ | |||
const int note = voice->getCurrentlyPlayingNote(); | |||
if (bottom == nullptr || note < bottom->getCurrentlyPlayingNote()) | |||
bottom = voice; | |||
if (low == nullptr || note < low->getCurrentlyPlayingNote()) | |||
low = voice; | |||
if (top == nullptr || note > top->getCurrentlyPlayingNote()) | |||
top = voice; | |||
if (top == nullptr || note > top->getCurrentlyPlayingNote()) | |||
top = voice; | |||
} | |||
} | |||
} | |||
// Eliminate pathological cases (ie: only 1 note playing): we always give precedence to the lowest note(s) | |||
if (top == low) | |||
top = nullptr; | |||
const int numUsableVoices = usableVoices.size(); | |||
// The oldest note that's playing with the target pitch playing is ideal.. | |||
// The oldest note that's playing with the target pitch is ideal.. | |||
for (int i = 0; i < numUsableVoices; ++i) | |||
{ | |||
SynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||
@@ -547,7 +578,7 @@ SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, | |||
{ | |||
SynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||
if (voice != bottom && voice != top && ! voice->isKeyDown() && ! voice->isSostenutoPedalDown()) | |||
if (voice != low && voice != top && voice->isPlayingButReleased()) | |||
return voice; | |||
} | |||
@@ -556,21 +587,25 @@ SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, | |||
{ | |||
SynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||
if (voice != bottom && voice != top && ! voice->isKeyDown()) | |||
if (voice != low && voice != top && ! voice->isKeyDown()) | |||
return voice; | |||
} | |||
// At this point, all notes have fingers on them, so look for the oldest note | |||
// that isn't the top or bottom note.. | |||
// Oldest voice that isn't protected | |||
for (int i = 0; i < numUsableVoices; ++i) | |||
{ | |||
SynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||
if (voice != bottom && voice != top) | |||
if (voice != low && voice != top) | |||
return voice; | |||
} | |||
// ..otherwise, there's only one or two voices to choose from - prefer to steal the highest one: | |||
jassert (top != nullptr || bottom != nullptr); | |||
return (top == nullptr ? bottom : top); | |||
// We've only got "protected" voices now: lowest note takes priority | |||
jassert (low != nullptr); | |||
// Duophonic synth: give priority to the bass note: | |||
if (top != nullptr) | |||
return top; | |||
return low; | |||
} |
@@ -214,9 +214,18 @@ public: | |||
*/ | |||
bool isKeyDown() const noexcept { return keyIsDown; } | |||
/** Returns true if the sustain pedal is currently active for this voice. */ | |||
bool isSustainPedalDown() const noexcept { return sustainPedalDown; } | |||
/** Returns true if the sostenuto pedal is currently active for this voice. */ | |||
bool isSostenutoPedalDown() const noexcept { return sostenutoPedalDown; } | |||
/** Returns true if a voice is sounding in its release phase **/ | |||
bool isPlayingButReleased() const noexcept | |||
{ | |||
return isVoiceActive() && ! (isKeyDown() || isSostenutoPedalDown() || isSustainPedalDown()); | |||
} | |||
/** Returns true if this voice started playing its current note before the other voice did. */ | |||
bool wasStartedBefore (const SynthesiserVoice& other) const noexcept; | |||
@@ -244,7 +253,7 @@ private: | |||
int currentlyPlayingNote, currentPlayingMidiChannel; | |||
uint32 noteOnTime; | |||
SynthesiserSound::Ptr currentlyPlayingSound; | |||
bool keyIsDown, sostenutoPedalDown; | |||
bool keyIsDown, sustainPedalDown, sostenutoPedalDown; | |||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE | |||
// Note the new parameters for this method. | |||
@@ -45,6 +45,14 @@ namespace CoreMidiHelpers | |||
#define CHECK_ERROR(a) CoreMidiHelpers::checkError (a, __LINE__) | |||
//============================================================================== | |||
struct ScopedCFString | |||
{ | |||
ScopedCFString() noexcept : cfString (nullptr) {} | |||
~ScopedCFString() noexcept { if (cfString != nullptr) CFRelease (cfString); } | |||
CFStringRef cfString; | |||
}; | |||
static String getMidiObjectName (MIDIObjectRef entity) | |||
{ | |||
String result; | |||
@@ -116,7 +124,7 @@ namespace CoreMidiHelpers | |||
if (numConnections > 0) | |||
{ | |||
const SInt32* pid = reinterpret_cast <const SInt32*> (CFDataGetBytePtr (connections)); | |||
const SInt32* pid = reinterpret_cast<const SInt32*> (CFDataGetBytePtr (connections)); | |||
for (int i = 0; i < numConnections; ++i, ++pid) | |||
{ | |||
@@ -133,7 +141,7 @@ namespace CoreMidiHelpers | |||
|| connObjectType == kMIDIObjectType_ExternalDestination) | |||
{ | |||
// Connected to an external device's endpoint (10.3 and later). | |||
s = getEndpointName (static_cast <MIDIEndpointRef> (connObject), true); | |||
s = getEndpointName (static_cast<MIDIEndpointRef> (connObject), true); | |||
} | |||
else | |||
{ | |||
@@ -208,9 +216,9 @@ namespace CoreMidiHelpers | |||
// correctly when called from the message thread! | |||
jassert (MessageManager::getInstance()->isThisTheMessageThread()); | |||
CFStringRef name = getGlobalMidiClientName().toCFString(); | |||
CHECK_ERROR (MIDIClientCreate (name, &globalSystemChangeCallback, nullptr, &globalMidiClient)); | |||
CFRelease (name); | |||
CoreMidiHelpers::ScopedCFString name; | |||
name.cfString = getGlobalMidiClientName().toCFString(); | |||
CHECK_ERROR (MIDIClientCreate (name.cfString, &globalSystemChangeCallback, nullptr, &globalMidiClient)); | |||
} | |||
return globalMidiClient; | |||
@@ -220,12 +228,12 @@ namespace CoreMidiHelpers | |||
class MidiPortAndEndpoint | |||
{ | |||
public: | |||
MidiPortAndEndpoint (MIDIPortRef p, MIDIEndpointRef ep) | |||
MidiPortAndEndpoint (MIDIPortRef p, MIDIEndpointRef ep) noexcept | |||
: port (p), endPoint (ep) | |||
{ | |||
} | |||
~MidiPortAndEndpoint() | |||
~MidiPortAndEndpoint() noexcept | |||
{ | |||
if (port != 0) | |||
MIDIPortDispose (port); | |||
@@ -234,7 +242,7 @@ namespace CoreMidiHelpers | |||
MIDIEndpointDispose (endPoint); | |||
} | |||
void send (const MIDIPacketList* const packets) | |||
void send (const MIDIPacketList* const packets) noexcept | |||
{ | |||
if (port != 0) | |||
MIDISend (port, endPoint, packets); | |||
@@ -302,7 +310,7 @@ namespace CoreMidiHelpers | |||
static void midiInputProc (const MIDIPacketList* pktlist, void* readProcRefCon, void* /*srcConnRefCon*/) | |||
{ | |||
static_cast <MidiPortAndCallback*> (readProcRefCon)->handlePackets (pktlist); | |||
static_cast<MidiPortAndCallback*> (readProcRefCon)->handlePackets (pktlist); | |||
} | |||
} | |||
@@ -318,19 +326,18 @@ MidiOutput* MidiOutput::openDevice (int index) | |||
{ | |||
MIDIEndpointRef endPoint = MIDIGetDestination ((ItemCount) index); | |||
CFStringRef pname; | |||
if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &pname))) | |||
CoreMidiHelpers::ScopedCFString pname; | |||
if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &pname.cfString))) | |||
{ | |||
MIDIClientRef client = CoreMidiHelpers::getGlobalMidiClient(); | |||
MIDIPortRef port; | |||
if (client != 0 && CHECK_ERROR (MIDIOutputPortCreate (client, pname, &port))) | |||
if (client != 0 && CHECK_ERROR (MIDIOutputPortCreate (client, pname.cfString, &port))) | |||
{ | |||
mo = new MidiOutput(); | |||
mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (port, endPoint); | |||
} | |||
CFRelease (pname); | |||
} | |||
} | |||
@@ -339,20 +346,20 @@ MidiOutput* MidiOutput::openDevice (int index) | |||
MidiOutput* MidiOutput::createNewDevice (const String& deviceName) | |||
{ | |||
MidiOutput* mo = nullptr; | |||
MIDIClientRef client = CoreMidiHelpers::getGlobalMidiClient(); | |||
MIDIEndpointRef endPoint; | |||
CFStringRef name = deviceName.toCFString(); | |||
if (client != 0 && CHECK_ERROR (MIDISourceCreate (client, name, &endPoint))) | |||
CoreMidiHelpers::ScopedCFString name; | |||
name.cfString = deviceName.toCFString(); | |||
if (client != 0 && CHECK_ERROR (MIDISourceCreate (client, name.cfString, &endPoint))) | |||
{ | |||
mo = new MidiOutput(); | |||
MidiOutput* mo = new MidiOutput(); | |||
mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (0, endPoint); | |||
return mo; | |||
} | |||
CFRelease (name); | |||
return mo; | |||
return nullptr; | |||
} | |||
MidiOutput::~MidiOutput() | |||
@@ -370,7 +377,7 @@ void MidiOutput::sendMessageNow (const MidiMessage& message) | |||
const MIDITimeStamp timeStamp = AudioGetCurrentHostTime(); | |||
#endif | |||
HeapBlock <MIDIPacketList> allocatedPackets; | |||
HeapBlock<MIDIPacketList> allocatedPackets; | |||
MIDIPacketList stackPacket; | |||
MIDIPacketList* packetToSend = &stackPacket; | |||
const size_t dataSize = (size_t) message.getRawDataSize(); | |||
@@ -436,16 +443,16 @@ MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) | |||
{ | |||
if (MIDIEndpointRef endPoint = MIDIGetSource ((ItemCount) index)) | |||
{ | |||
CFStringRef name; | |||
ScopedCFString name; | |||
if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &name))) | |||
if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &name.cfString))) | |||
{ | |||
if (MIDIClientRef client = getGlobalMidiClient()) | |||
{ | |||
MIDIPortRef port; | |||
ScopedPointer <MidiPortAndCallback> mpc (new MidiPortAndCallback (*callback)); | |||
ScopedPointer<MidiPortAndCallback> mpc (new MidiPortAndCallback (*callback)); | |||
if (CHECK_ERROR (MIDIInputPortCreate (client, name, midiInputProc, mpc, &port))) | |||
if (CHECK_ERROR (MIDIInputPortCreate (client, name.cfString, midiInputProc, mpc, &port))) | |||
{ | |||
if (CHECK_ERROR (MIDIPortConnectSource (port, endPoint, nullptr))) | |||
{ | |||
@@ -465,8 +472,6 @@ MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) | |||
} | |||
} | |||
} | |||
CFRelease (name); | |||
} | |||
} | |||
@@ -482,13 +487,14 @@ MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallba | |||
if (MIDIClientRef client = getGlobalMidiClient()) | |||
{ | |||
ScopedPointer <MidiPortAndCallback> mpc (new MidiPortAndCallback (*callback)); | |||
ScopedPointer<MidiPortAndCallback> mpc (new MidiPortAndCallback (*callback)); | |||
mpc->active = false; | |||
MIDIEndpointRef endPoint; | |||
CFStringRef name = deviceName.toCFString(); | |||
ScopedCFString name; | |||
name.cfString = deviceName.toCFString(); | |||
if (CHECK_ERROR (MIDIDestinationCreate (client, name, midiInputProc, mpc, &endPoint))) | |||
if (CHECK_ERROR (MIDIDestinationCreate (client, name.cfString, midiInputProc, mpc, &endPoint))) | |||
{ | |||
mpc->portAndEndpoint = new MidiPortAndEndpoint (0, endPoint); | |||
@@ -499,8 +505,6 @@ MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallba | |||
const ScopedLock sl (callbackLock); | |||
activeCallbacks.add (mpc.release()); | |||
} | |||
CFRelease (name); | |||
} | |||
return mi; | |||
@@ -22,6 +22,9 @@ | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_INCLUDEMODULEHEADERS_H_INCLUDED | |||
#define JUCE_INCLUDEMODULEHEADERS_H_INCLUDED | |||
#include "../juce_audio_plugin_client.h" | |||
using namespace juce; | |||
@@ -44,3 +47,5 @@ namespace juce | |||
} | |||
extern AudioProcessor* JUCE_CALLTYPE createPluginFilterOfType (AudioProcessor::WrapperType); | |||
#endif // JUCE_INCLUDEMODULEHEADERS_H_INCLUDED |
@@ -31,6 +31,7 @@ | |||
#include "AppConfig.h" | |||
#include "../utility/juce_CheckSettingMacros.h" | |||
#include "juce_IncludeModuleHeaders.h" | |||
#if _MSC_VER || JUCE_MINGW | |||
@@ -916,6 +916,11 @@ public: | |||
const File temp (File::getSpecialLocation (File::tempDirectory)); | |||
expect (! File::nonexistent.exists()); | |||
expect (! File::nonexistent.existsAsFile()); | |||
expect (! File::nonexistent.isDirectory()); | |||
#if ! JUCE_WINDOWS | |||
expect (File("/").isDirectory()); | |||
#endif | |||
expect (home.isDirectory()); | |||
expect (home.exists()); | |||
expect (! home.existsAsFile()); | |||
@@ -50,7 +50,7 @@ inline void deleteAndZero (Type& pointer) { delete poi | |||
a specific number of bytes, | |||
*/ | |||
template <typename Type, typename IntegerType> | |||
inline Type* addBytesToPointer (Type* pointer, IntegerType bytes) noexcept { return (Type*) (((char*) pointer) + bytes); } | |||
inline Type* addBytesToPointer (Type* basePointer, IntegerType bytes) noexcept { return (Type*) (((char*) basePointer) + bytes); } | |||
/** A handy function which returns the difference between any two pointers, in bytes. | |||
The address of the second pointer is subtracted from the first, and the difference in bytes is returned. | |||
@@ -62,7 +62,7 @@ inline int getAddressDifference (Type1* pointer1, Type2* pointer2) noexcept { r | |||
nullptr if the pointer is null. | |||
*/ | |||
template <class Type> | |||
inline Type* createCopyIfNotNull (const Type* pointer) { return pointer != nullptr ? new Type (*pointer) : nullptr; } | |||
inline Type* createCopyIfNotNull (const Type* objectToCopy) { return objectToCopy != nullptr ? new Type (*objectToCopy) : nullptr; } | |||
//============================================================================== | |||
#if JUCE_MAC || JUCE_IOS || DOXYGEN | |||
@@ -265,8 +265,8 @@ bool File::isDirectory() const | |||
{ | |||
juce_statStruct info; | |||
return fullPath.isEmpty() | |||
|| (juce_stat (fullPath, info) && ((info.st_mode & S_IFDIR) != 0)); | |||
return fullPath.isNotEmpty() | |||
&& (juce_stat (fullPath, info) && ((info.st_mode & S_IFDIR) != 0)); | |||
} | |||
bool File::exists() const | |||
@@ -89,12 +89,12 @@ public: | |||
*/ | |||
String (const char* text, size_t maxChars); | |||
/** Creates a string from a whcar_t character string. | |||
/** Creates a string from a wchar_t character string. | |||
Depending on the platform, this may be treated as either UTF-32 or UTF-16. | |||
*/ | |||
String (const wchar_t* text); | |||
/** Creates a string from a whcar_t character string. | |||
/** Creates a string from a wchar_t character string. | |||
Depending on the platform, this may be treated as either UTF-32 or UTF-16. | |||
*/ | |||
String (const wchar_t* text, size_t maxChars); | |||
@@ -32,8 +32,8 @@ namespace | |||
jassert ((int) x >= -maxVal && (int) x <= maxVal | |||
&& (int) y >= -maxVal && (int) y <= maxVal | |||
&& (int) w >= -maxVal && (int) w <= maxVal | |||
&& (int) h >= -maxVal && (int) h <= maxVal); | |||
&& (int) w >= 0 && (int) w <= maxVal | |||
&& (int) h >= 0 && (int) h <= maxVal); | |||
#endif | |||
return Rectangle<Type> (x, y, w, h); | |||
@@ -427,6 +427,8 @@ void Graphics::drawRect (const Rectangle<int>& r, int lineThickness) const | |||
void Graphics::drawRect (Rectangle<float> r, const float lineThickness) const | |||
{ | |||
jassert (r.getWidth() >= 0.0f && r.getHeight() >= 0.0f); | |||
RectangleList<float> rects; | |||
rects.addWithoutMerging (r.removeFromTop (lineThickness)); | |||
rects.addWithoutMerging (r.removeFromBottom (lineThickness)); | |||
@@ -1153,12 +1153,15 @@ void Component::setBounds (const int x, const int y, int w, int h) | |||
void Component::sendMovedResizedMessagesIfPending() | |||
{ | |||
if (flags.isMoveCallbackPending || flags.isResizeCallbackPending) | |||
{ | |||
sendMovedResizedMessages (flags.isMoveCallbackPending, flags.isResizeCallbackPending); | |||
const bool wasMoved = flags.isMoveCallbackPending; | |||
const bool wasResized = flags.isResizeCallbackPending; | |||
if (wasMoved || wasResized) | |||
{ | |||
flags.isMoveCallbackPending = false; | |||
flags.isResizeCallbackPending = false; | |||
sendMovedResizedMessages (wasMoved, wasResized); | |||
} | |||
} | |||
@@ -3723,12 +3723,45 @@ void Desktop::Displays::findDisplays (float masterScale) | |||
{ | |||
DisplayGeometry& geometry = DisplayGeometry::getOrCreateInstance (display, masterScale); | |||
// add the main display first | |||
int mainDisplayIdx; | |||
for (mainDisplayIdx = 0; mainDisplayIdx < geometry.infos.size(); ++mainDisplayIdx) | |||
{ | |||
const DisplayGeometry::ExtendedInfo& info = geometry.infos.getReference (mainDisplayIdx); | |||
if (info.isMain) | |||
break; | |||
} | |||
// no main display found then use the first | |||
if (mainDisplayIdx >= geometry.infos.size()) | |||
mainDisplayIdx = 0; | |||
// add the main display | |||
{ | |||
const DisplayGeometry::ExtendedInfo& info = | |||
geometry.infos.getReference (mainDisplayIdx); | |||
Desktop::Displays::Display d; | |||
d.isMain = true; | |||
d.scale = masterScale * info.scale; | |||
d.dpi = info.dpi; | |||
d.totalArea = DisplayGeometry::physicalToScaled (info.totalBounds); | |||
d.userArea = (info.usableBounds / d.scale) + info.topLeftScaled; | |||
displays.add (d); | |||
} | |||
for (int i = 0; i < geometry.infos.size(); ++i) | |||
{ | |||
// don't add the main display a second time | |||
if (i == mainDisplayIdx) | |||
continue; | |||
const DisplayGeometry::ExtendedInfo& info = geometry.infos.getReference (i); | |||
Desktop::Displays::Display d; | |||
d.isMain = info.isMain; | |||
d.isMain = false; | |||
d.scale = masterScale * info.scale; | |||
d.dpi = info.dpi; | |||