@@ -487,17 +487,14 @@ void AudioSampleBuffer::reverse (int startSample, int numSamples) const noexcept | |||
reverse (i, startSample, numSamples); | |||
} | |||
void AudioSampleBuffer::findMinMax (const int channel, | |||
const int startSample, | |||
int numSamples, | |||
float& minVal, | |||
float& maxVal) const noexcept | |||
Range<float> AudioSampleBuffer::findMinMax (const int channel, | |||
const int startSample, | |||
int numSamples) const noexcept | |||
{ | |||
jassert (isPositiveAndBelow (channel, numChannels)); | |||
jassert (startSample >= 0 && startSample + numSamples <= size); | |||
FloatVectorOperations::findMinAndMax (channels [channel] + startSample, | |||
numSamples, minVal, maxVal); | |||
return FloatVectorOperations::findMinAndMax (channels [channel] + startSample, numSamples); | |||
} | |||
float AudioSampleBuffer::getMagnitude (const int channel, | |||
@@ -507,10 +504,9 @@ float AudioSampleBuffer::getMagnitude (const int channel, | |||
jassert (isPositiveAndBelow (channel, numChannels)); | |||
jassert (startSample >= 0 && startSample + numSamples <= size); | |||
float mn, mx; | |||
findMinMax (channel, startSample, numSamples, mn, mx); | |||
const Range<float> r (findMinMax (channel, startSample, numSamples)); | |||
return jmax (mn, -mn, mx, -mx); | |||
return jmax (r.getStart(), -r.getStart(), r.getEnd(), -r.getEnd()); | |||
} | |||
float AudioSampleBuffer::getMagnitude (int startSample, int numSamples) const noexcept | |||
@@ -392,19 +392,15 @@ public: | |||
float endGain) noexcept; | |||
/** Finds the highest and lowest sample values in a given range. | |||
/** Returns a Range indicating the lowest and highest sample values in a given section. | |||
@param channel the channel to read from | |||
@param startSample the start sample within the channel | |||
@param numSamples the number of samples to check | |||
@param minVal on return, the lowest value that was found | |||
@param maxVal on return, the highest value that was found | |||
*/ | |||
void findMinMax (int channel, | |||
int startSample, | |||
int numSamples, | |||
float& minVal, | |||
float& maxVal) const noexcept; | |||
Range<float> findMinMax (int channel, | |||
int startSample, | |||
int numSamples) const noexcept; | |||
/** Finds the highest absolute sample value within a region of a channel. */ | |||
float getMagnitude (int channel, | |||
@@ -38,48 +38,90 @@ public: | |||
/** Clears a vector of floats. */ | |||
static void JUCE_CALLTYPE clear (float* dest, int numValues) noexcept; | |||
/** Clears a vector of doubles. */ | |||
static void JUCE_CALLTYPE clear (double* dest, int numValues) noexcept; | |||
/** Copies a repeated value into a vector of floats. */ | |||
static void JUCE_CALLTYPE fill (float* dest, float valueToFill, int numValues) noexcept; | |||
/** Copies a repeated value into a vector of doubles. */ | |||
static void JUCE_CALLTYPE fill (double* dest, double valueToFill, int numValues) noexcept; | |||
/** Copies a vector of floats. */ | |||
static void JUCE_CALLTYPE copy (float* dest, const float* src, int numValues) noexcept; | |||
/** Copies a vector of doubles. */ | |||
static void JUCE_CALLTYPE copy (double* dest, const double* src, int numValues) noexcept; | |||
/** Copies a vector of floats, multiplying each value by a given multiplier */ | |||
static void JUCE_CALLTYPE copyWithMultiply (float* dest, const float* src, float multiplier, int numValues) noexcept; | |||
/** Copies a vector of doubles, multiplying each value by a given multiplier */ | |||
static void JUCE_CALLTYPE copyWithMultiply (double* dest, const double* src, double multiplier, int numValues) noexcept; | |||
/** Adds a fixed value to the destination values. */ | |||
static void JUCE_CALLTYPE add (float* dest, float amountToAdd, int numValues) noexcept; | |||
/** Adds a fixed value to the destination values. */ | |||
static void JUCE_CALLTYPE add (float* dest, float amount, int numValues) noexcept; | |||
static void JUCE_CALLTYPE add (double* dest, double amountToAdd, int numValues) noexcept; | |||
/** Adds the source values to the destination values. */ | |||
static void JUCE_CALLTYPE add (float* dest, const float* src, int numValues) noexcept; | |||
/** Adds the source values to the destination values. */ | |||
static void JUCE_CALLTYPE add (double* dest, const double* src, int numValues) noexcept; | |||
/** Subtracts the source values from the destination values. */ | |||
static void JUCE_CALLTYPE subtract (float* dest, const float* src, int numValues) noexcept; | |||
/** Subtracts the source values from the destination values. */ | |||
static void JUCE_CALLTYPE subtract (double* dest, const double* src, int numValues) noexcept; | |||
/** Multiplies each source value by the given multiplier, then adds it to the destination value. */ | |||
static void JUCE_CALLTYPE addWithMultiply (float* dest, const float* src, float multiplier, int numValues) noexcept; | |||
/** Multiplies each source value by the given multiplier, then adds it to the destination value. */ | |||
static void JUCE_CALLTYPE addWithMultiply (double* dest, const double* src, double multiplier, int numValues) noexcept; | |||
/** Multiplies 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 of the destination values by a fixed multiplier. */ | |||
static void JUCE_CALLTYPE multiply (float* dest, float multiplier, int numValues) noexcept; | |||
/** Multiplies each of the destination values by a fixed multiplier. */ | |||
static void JUCE_CALLTYPE multiply (double* dest, double multiplier, int numValues) noexcept; | |||
/** Copies a source vector to a destination, negating each value. */ | |||
static void JUCE_CALLTYPE negate (float* dest, const float* src, int numValues) noexcept; | |||
/** Copies a source vector to a destination, negating each value. */ | |||
static void JUCE_CALLTYPE negate (double* dest, const double* src, int numValues) noexcept; | |||
/** Converts a stream of integers to floats, multiplying each one by the given multiplier. */ | |||
static void JUCE_CALLTYPE convertFixedToFloat (float* dest, const int* src, float multiplier, int numValues) noexcept; | |||
/** Finds the miniumum and maximum values in the given array. */ | |||
static void JUCE_CALLTYPE findMinAndMax (const float* src, int numValues, float& minResult, float& maxResult) noexcept; | |||
static Range<float> JUCE_CALLTYPE findMinAndMax (const float* src, int numValues) noexcept; | |||
/** Finds the miniumum and maximum values in the given array. */ | |||
static Range<double> JUCE_CALLTYPE findMinAndMax (const double* src, int numValues) noexcept; | |||
/** Finds the miniumum value in the given array. */ | |||
static float JUCE_CALLTYPE findMinimum (const float* src, int numValues) noexcept; | |||
/** Finds the miniumum value in the given array. */ | |||
static double JUCE_CALLTYPE findMinimum (const double* src, int numValues) noexcept; | |||
/** Finds the maximum value in the given array. */ | |||
static float JUCE_CALLTYPE findMaximum (const float* src, int numValues) noexcept; | |||
/** Finds the maximum value in the given array. */ | |||
static double JUCE_CALLTYPE findMaximum (const double* src, int numValues) noexcept; | |||
/** On Intel CPUs, this method enables or disables the SSE flush-to-zero mode. | |||
Effectively, this is a wrapper around a call to _MM_SET_FLUSH_ZERO_MODE | |||
*/ | |||
@@ -36,6 +36,10 @@ | |||
#include "AppConfig.h" | |||
#include "juce_audio_basics.h" | |||
#if JUCE_MINGW && ! defined (__SSE2__) | |||
#define JUCE_USE_SSE_INTRINSICS 0 | |||
#endif | |||
#ifndef JUCE_USE_SSE_INTRINSICS | |||
#define JUCE_USE_SSE_INTRINSICS 1 | |||
#endif | |||
@@ -350,11 +350,13 @@ void MidiFile::convertTimestampTicksToSeconds() | |||
} | |||
//============================================================================== | |||
bool MidiFile::writeTo (OutputStream& out) | |||
bool MidiFile::writeTo (OutputStream& out, int midiFileType) | |||
{ | |||
jassert (midiFileType >= 0 && midiFileType <= 2); | |||
out.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MThd")); | |||
out.writeIntBigEndian (6); | |||
out.writeShortBigEndian (1); // type | |||
out.writeShortBigEndian ((short) midiFileType); | |||
out.writeShortBigEndian ((short) tracks.size()); | |||
out.writeShortBigEndian (timeFormat); | |||
@@ -151,9 +151,11 @@ public: | |||
bool readFrom (InputStream& sourceStream); | |||
/** Writes the midi tracks as a standard midi file. | |||
The midiFileType value is written as the file's format type, which can be 0, 1 | |||
or 2 - see the midi file spec for more info about that. | |||
@returns true if the operation succeeded. | |||
*/ | |||
bool writeTo (OutputStream& destStream); | |||
bool writeTo (OutputStream& destStream, int midiFileType = 1); | |||
/** Converts the timestamp of all the midi events from midi ticks to seconds. | |||
@@ -152,7 +152,8 @@ MidiMessage::MidiMessage (const MidiMessage& other, const double newTimeStamp) | |||
} | |||
} | |||
MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const uint8 lastStatusByte, double t) | |||
MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const uint8 lastStatusByte, | |||
double t, bool sysexHasEmbeddedLength) | |||
: timeStamp (t) | |||
{ | |||
const uint8* src = static_cast<const uint8*> (srcData); | |||
@@ -175,7 +176,7 @@ MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const | |||
if (byte == 0xf0) | |||
{ | |||
const uint8* d = src; | |||
bool haveReadAllLengthBytes = false; | |||
bool haveReadAllLengthBytes = ! sysexHasEmbeddedLength; | |||
int numVariableLengthSysexBytes = 0; | |||
while (d < src + sz) | |||
@@ -82,10 +82,14 @@ public: | |||
has in fact been dropped. | |||
@param timeStamp the time to give the midi message - this value doesn't | |||
use any particular units, so will be application-specific | |||
@param sysexHasEmbeddedLength when reading sysexes, this flag indicates whether | |||
to expect the data to begin with a variable-length field | |||
indicating its size | |||
*/ | |||
MidiMessage (const void* data, int maxBytesToUse, | |||
int& numBytesUsed, uint8 lastStatusByte, | |||
double timeStamp = 0); | |||
double timeStamp = 0, | |||
bool sysexHasEmbeddedLength = true); | |||
/** Creates an active-sense message. | |||
Since the MidiMessage has to contain a valid message, this default constructor | |||
@@ -94,10 +98,10 @@ public: | |||
MidiMessage() noexcept; | |||
/** Creates a copy of another midi message. */ | |||
MidiMessage (const MidiMessage& other); | |||
MidiMessage (const MidiMessage&); | |||
/** Creates a copy of another midi message, with a different timestamp. */ | |||
MidiMessage (const MidiMessage& other, double newTimeStamp); | |||
MidiMessage (const MidiMessage&, double newTimeStamp); | |||
/** Destructor. */ | |||
~MidiMessage(); | |||
@@ -106,8 +110,8 @@ public: | |||
MidiMessage& operator= (const MidiMessage& other); | |||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
MidiMessage (MidiMessage&& other) noexcept; | |||
MidiMessage& operator= (MidiMessage&& other) noexcept; | |||
MidiMessage (MidiMessage&&) noexcept; | |||
MidiMessage& operator= (MidiMessage&&) noexcept; | |||
#endif | |||
//============================================================================== | |||
@@ -58,6 +58,11 @@ void SynthesiserVoice::clearCurrentNote() | |||
void SynthesiserVoice::aftertouchChanged (int) {} | |||
bool SynthesiserVoice::wasStartedBefore (const SynthesiserVoice& other) const noexcept | |||
{ | |||
return noteOnTime < other.noteOnTime; | |||
} | |||
//============================================================================== | |||
Synthesiser::Synthesiser() | |||
: sampleRate (0), | |||
@@ -407,12 +412,11 @@ void Synthesiser::handleSoftPedal (int midiChannel, bool /*isDown*/) | |||
} | |||
//============================================================================== | |||
SynthesiserVoice* Synthesiser::findFreeVoice (SynthesiserSound* soundToPlay, | |||
const bool stealIfNoneAvailable) const | |||
SynthesiserVoice* Synthesiser::findFreeVoice (SynthesiserSound* soundToPlay, const bool stealIfNoneAvailable) const | |||
{ | |||
const ScopedLock sl (lock); | |||
for (int i = voices.size(); --i >= 0;) | |||
for (int i = 0; i < voices.size(); ++i) | |||
{ | |||
SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
@@ -421,22 +425,25 @@ SynthesiserVoice* Synthesiser::findFreeVoice (SynthesiserSound* soundToPlay, | |||
} | |||
if (stealIfNoneAvailable) | |||
{ | |||
// currently this just steals the one that's been playing the longest, but could be made a bit smarter.. | |||
SynthesiserVoice* oldest = nullptr; | |||
return findVoiceToSteal (soundToPlay); | |||
for (int i = voices.size(); --i >= 0;) | |||
{ | |||
SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
return nullptr; | |||
} | |||
if (voice->canPlaySound (soundToPlay) | |||
&& (oldest == nullptr || oldest->noteOnTime > voice->noteOnTime)) | |||
oldest = voice; | |||
} | |||
SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay) const | |||
{ | |||
// currently this just steals the one that's been playing the longest, but could be made a bit smarter.. | |||
SynthesiserVoice* oldest = nullptr; | |||
jassert (oldest != nullptr); | |||
return oldest; | |||
for (int i = 0; i < voices.size(); ++i) | |||
{ | |||
SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
if (voice->canPlaySound (soundToPlay) | |||
&& (oldest == nullptr || voice->wasStartedBefore (*oldest))) | |||
oldest = voice; | |||
} | |||
return nullptr; | |||
jassert (oldest != nullptr); | |||
return oldest; | |||
} |
@@ -199,6 +199,9 @@ public: | |||
/** Returns true if the sostenuto pedal is currently active for this voice. */ | |||
bool isSostenutoPedalDown() const noexcept { return sostenutoPedalDown; } | |||
/** Returns true if this voice started playing its current note before the other voice did. */ | |||
bool wasStartedBefore (const SynthesiserVoice& other) const noexcept; | |||
protected: | |||
//============================================================================== | |||
/** Returns the current target sample rate at which rendering is being done. | |||
@@ -481,6 +484,12 @@ protected: | |||
virtual SynthesiserVoice* findFreeVoice (SynthesiserSound* soundToPlay, | |||
const bool stealIfNoneAvailable) const; | |||
/** Chooses a voice that is most suitable for being re-used. | |||
The default method returns the one that has been playing for the longest, but | |||
you may want to override this and do something more cunning instead. | |||
*/ | |||
virtual SynthesiserVoice* findVoiceToSteal (SynthesiserSound* soundToPlay) const; | |||
/** Starts a specified voice playing a particular sound. | |||
You'll probably never need to call this, it's used internally by noteOn(), but | |||
@@ -632,7 +632,7 @@ public: | |||
{ | |||
closeClient(); | |||
captureClient = nullptr; | |||
reservoir.setSize (0); | |||
reservoir.reset(); | |||
} | |||
template <class SourceType> | |||
@@ -61,6 +61,7 @@ namespace FlacNamespace | |||
#define FLAC__HAS_X86INTRIN 1 | |||
#endif | |||
#undef __STDC_LIMIT_MACROS | |||
#define __STDC_LIMIT_MACROS 1 | |||
#define flac_max jmax | |||
#define flac_min jmin | |||
@@ -516,12 +516,21 @@ struct MP3Frame | |||
{ 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 } } | |||
}; | |||
switch (layer) | |||
if (bitrateIndex == 0) | |||
{ | |||
case 1: frameSize = (((frameSizes[lsf][0][bitrateIndex] * 12000) / getFrequency() + padding) * 4) - 4; break; | |||
case 2: frameSize = (frameSizes[lsf][1][bitrateIndex] * 144000) / getFrequency() + (padding - 4); break; | |||
case 3: frameSize = (bitrateIndex == 0) ? 0 : ((frameSizes[lsf][2][bitrateIndex] * 144000) / (getFrequency() << lsf) + (padding - 4)); break; | |||
default: break; | |||
jassertfalse; // This means the file is using "free format". Apparently very few decoders | |||
// support this mode, and this one certainly doesn't handle it correctly! | |||
frameSize = 0; | |||
} | |||
else | |||
{ | |||
switch (layer) | |||
{ | |||
case 1: frameSize = (((frameSizes[lsf][0][bitrateIndex] * 12000) / getFrequency() + padding) * 4) - 4; break; | |||
case 2: frameSize = (frameSizes[lsf][1][bitrateIndex] * 144000) / getFrequency() + (padding - 4); break; | |||
case 3: frameSize = (bitrateIndex == 0) ? 0 : ((frameSizes[lsf][2][bitrateIndex] * 144000) / (getFrequency() << lsf) + (padding - 4)); break; | |||
default: break; | |||
} | |||
} | |||
} | |||
@@ -1451,7 +1460,7 @@ struct MP3Stream | |||
bufferPointer = bufferSpace[bufferSpaceIndex] + 512; | |||
bitIndex = 0; | |||
if (lastFrameSize == -1) | |||
if (lastFrameSize < 0) | |||
return 1; | |||
} | |||
@@ -1513,8 +1522,14 @@ struct MP3Stream | |||
else | |||
{ | |||
const int nextFrameOffset = scanForNextFrameHeader (true); | |||
wasFreeFormat = isFreeFormat; | |||
if (nextFrameOffset < 0) | |||
{ | |||
lastFrameSize = frameSize; | |||
return result; | |||
} | |||
frameSize = nextFrameOffset + sideInfoSize + dataSize; | |||
lastFrameSizeNoPadding = frameSize - frame.padding; | |||
@@ -983,11 +983,13 @@ private: | |||
switch (numChannels) | |||
{ | |||
case 1: return 0; | |||
case 2: return 1 + 2; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | |||
case 5: return 1 + 2 + 4 + 16 + 32; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | |||
case 6: return 1 + 2 + 4 + 8 + 16 + 32; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | |||
case 7: return 1 + 2 + 4 + 16 + 32 + 512 + 1024; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT | |||
case 8: return 1 + 2 + 4 + 8 + 16 + 32 + 512 + 1024; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT | |||
case 2: return 1 + 2; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | |||
case 3: return 1 + 2 + 4; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | |||
case 4: return 1 + 2 + 16 + 32; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | |||
case 5: return 1 + 2 + 4 + 16 + 32; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | |||
case 6: return 1 + 2 + 4 + 8 + 16 + 32; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | |||
case 7: return 1 + 2 + 4 + 16 + 32 + 512 + 1024; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT | |||
case 8: return 1 + 2 + 4 + 8 + 16 + 32 + 512 + 1024; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT | |||
default: break; | |||
} | |||
@@ -344,6 +344,7 @@ Array<int> WindowsMediaAudioFormat::getPossibleBitDepths() { return Array<i | |||
bool WindowsMediaAudioFormat::canDoStereo() { return true; } | |||
bool WindowsMediaAudioFormat::canDoMono() { return true; } | |||
bool WindowsMediaAudioFormat::isCompressed() { return true; } | |||
//============================================================================== | |||
AudioFormatReader* WindowsMediaAudioFormat::createReaderFor (InputStream* sourceStream, bool deleteStreamIfOpeningFails) | |||
@@ -40,6 +40,7 @@ public: | |||
Array<int> getPossibleBitDepths() override; | |||
bool canDoStereo() override; | |||
bool canDoMono() override; | |||
bool isCompressed() override; | |||
//============================================================================== | |||
AudioFormatReader* createReaderFor (InputStream*, bool deleteStreamIfOpeningFails) override; | |||
@@ -174,30 +174,29 @@ void AudioFormatReader::read (AudioSampleBuffer* buffer, | |||
} | |||
template <typename SampleType> | |||
static inline void getChannelMinAndMax (SampleType* channel, const int numSamples, SampleType& mn, SampleType& mx) | |||
static Range<SampleType> getChannelMinAndMax (SampleType* channel, int numSamples) noexcept | |||
{ | |||
findMinAndMax (channel, numSamples, mn, mx); | |||
return Range<SampleType>::findMinAndMax (channel, numSamples); | |||
} | |||
static inline void getChannelMinAndMax (float* channel, const int numSamples, float& mn, float& mx) | |||
static Range<float> getChannelMinAndMax (float* channel, int numSamples) noexcept | |||
{ | |||
FloatVectorOperations::findMinAndMax (channel, numSamples, mn, mx); | |||
return FloatVectorOperations::findMinAndMax (channel, numSamples); | |||
} | |||
template <typename SampleType> | |||
static void getStereoMinAndMax (SampleType* const* channels, const int numChannels, const int numSamples, | |||
SampleType& lmin, SampleType& lmax, SampleType& rmin, SampleType& rmax) | |||
{ | |||
SampleType bufMin, bufMax; | |||
getChannelMinAndMax (channels[0], numSamples, bufMin, bufMax); | |||
lmax = jmax (lmax, bufMax); | |||
lmin = jmin (lmin, bufMin); | |||
Range<SampleType> range (getChannelMinAndMax (channels[0], numSamples)); | |||
lmax = jmax (lmax, range.getEnd()); | |||
lmin = jmin (lmin, range.getStart()); | |||
if (numChannels > 1) | |||
{ | |||
getChannelMinAndMax (channels[1], numSamples, bufMin, bufMax); | |||
rmax = jmax (rmax, bufMax); | |||
rmin = jmin (rmin, bufMin); | |||
range = getChannelMinAndMax (channels[1], numSamples); | |||
rmax = jmax (rmax, range.getEnd()); | |||
rmin = jmin (rmin, range.getStart()); | |||
} | |||
else | |||
{ | |||
@@ -442,7 +442,7 @@ struct AAXClasses | |||
if (chunkID != juceChunkType) | |||
return AAX_CEffectParameters::GetChunkSize (chunkID, oSize); | |||
tempFilterData.setSize (0); | |||
tempFilterData.reset(); | |||
pluginInstance->getStateInformation (tempFilterData); | |||
*oSize = (uint32_t) tempFilterData.getSize(); | |||
return AAX_SUCCESS; | |||
@@ -458,7 +458,7 @@ struct AAXClasses | |||
oChunk->fSize = (int32_t) tempFilterData.getSize(); | |||
tempFilterData.copyTo (oChunk->fData, 0, tempFilterData.getSize()); | |||
tempFilterData.setSize (0); | |||
tempFilterData.reset(); | |||
return AAX_SUCCESS; | |||
} | |||
@@ -627,7 +627,7 @@ protected: | |||
{ | |||
if (chunkID == juceChunkType) | |||
{ | |||
tempFilterData.setSize (0); | |||
tempFilterData.reset(); | |||
juceFilter->getStateInformation (tempFilterData); | |||
*size = sizeof (SFicPlugInChunkHeader) + tempFilterData.getSize(); | |||
@@ -647,7 +647,7 @@ protected: | |||
chunk->fSize = sizeof (SFicPlugInChunkHeader) + tempFilterData.getSize(); | |||
tempFilterData.copyTo ((void*) chunk->fData, 0, tempFilterData.getSize()); | |||
tempFilterData.setSize (0); | |||
tempFilterData.reset(); | |||
return noErr; | |||
} | |||
@@ -659,7 +659,7 @@ protected: | |||
{ | |||
if (chunkID == juceChunkType) | |||
{ | |||
tempFilterData.setSize (0); | |||
tempFilterData.reset(); | |||
if (chunk->fSize - sizeof (SFicPlugInChunkHeader) > 0) | |||
{ | |||
@@ -96,6 +96,7 @@ | |||
#include "../utility/juce_IncludeModuleHeaders.h" | |||
#include "../utility/juce_FakeMouseMoveGenerator.h" | |||
#include "../utility/juce_WindowsHooks.h" | |||
#include "modules/juce_audio_processors/format_types/juce_VSTMidiEventList.h" | |||
#ifdef _MSC_VER | |||
@@ -169,48 +170,7 @@ namespace | |||
return w; | |||
} | |||
//============================================================================== | |||
static HHOOK mouseWheelHook = 0; | |||
static int mouseHookUsers = 0; | |||
LRESULT CALLBACK mouseWheelHookCallback (int nCode, WPARAM wParam, LPARAM lParam) | |||
{ | |||
if (nCode >= 0 && wParam == WM_MOUSEWHEEL) | |||
{ | |||
// using a local copy of this struct to support old mingw libraries | |||
struct MOUSEHOOKSTRUCTEX_ : public MOUSEHOOKSTRUCT { DWORD mouseData; }; | |||
const MOUSEHOOKSTRUCTEX_& hs = *(MOUSEHOOKSTRUCTEX_*) lParam; | |||
if (Component* const comp = Desktop::getInstance().findComponentAt (Point<int> (hs.pt.x, hs.pt.y))) | |||
if (comp->getWindowHandle() != 0) | |||
return PostMessage ((HWND) comp->getWindowHandle(), WM_MOUSEWHEEL, | |||
hs.mouseData & 0xffff0000, (hs.pt.x & 0xffff) | (hs.pt.y << 16)); | |||
} | |||
return CallNextHookEx (mouseWheelHook, nCode, wParam, lParam); | |||
} | |||
void registerMouseWheelHook() | |||
{ | |||
if (mouseHookUsers++ == 0) | |||
mouseWheelHook = SetWindowsHookEx (WH_MOUSE, mouseWheelHookCallback, | |||
(HINSTANCE) Process::getCurrentModuleInstanceHandle(), | |||
GetCurrentThreadId()); | |||
} | |||
void unregisterMouseWheelHook() | |||
{ | |||
if (--mouseHookUsers == 0 && mouseWheelHook != 0) | |||
{ | |||
UnhookWindowsHookEx (mouseWheelHook); | |||
mouseWheelHook = 0; | |||
} | |||
} | |||
#if JUCE_WINDOWS | |||
static bool messageThreadIsDefinitelyCorrect = false; | |||
#endif | |||
} | |||
//============================================================================== | |||
@@ -978,7 +938,7 @@ public: | |||
if (filter == nullptr) | |||
return 0; | |||
chunkMemory.setSize (0); | |||
chunkMemory.reset(); | |||
if (onlyStoreCurrentProgramData) | |||
filter->getCurrentProgramStateInformation (chunkMemory); | |||
else | |||
@@ -997,7 +957,7 @@ public: | |||
{ | |||
if (filter != nullptr) | |||
{ | |||
chunkMemory.setSize (0); | |||
chunkMemory.reset(); | |||
chunkMemoryTime = 0; | |||
if (byteSize > 0 && data != nullptr) | |||
@@ -1024,8 +984,8 @@ public: | |||
&& chunkMemoryTime < juce::Time::getApproximateMillisecondCounter() - 2000 | |||
&& ! recursionCheck) | |||
{ | |||
chunkMemory.reset(); | |||
chunkMemoryTime = 0; | |||
chunkMemory.setSize (0); | |||
} | |||
#if JUCE_MAC | |||
@@ -1302,17 +1262,11 @@ public: | |||
#if JUCE_WINDOWS | |||
if (! getHostType().isReceptor()) | |||
addMouseListener (this, true); | |||
registerMouseWheelHook(); | |||
#endif | |||
} | |||
~EditorCompWrapper() | |||
{ | |||
#if JUCE_WINDOWS | |||
unregisterMouseWheelHook(); | |||
#endif | |||
deleteAllChildren(); // note that we can't use a ScopedPointer because the editor may | |||
// have been transferred to another parent which takes over ownership. | |||
} | |||
@@ -1403,6 +1357,10 @@ public: | |||
JuceVSTWrapper& wrapper; | |||
FakeMouseMoveGenerator fakeMouseGenerator; | |||
#if JUCE_WINDOWS | |||
WindowsHooks hooks; | |||
#endif | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EditorCompWrapper) | |||
}; | |||
@@ -32,8 +32,26 @@ | |||
#include "../../juce_audio_processors/format_types/juce_VST3Headers.h" | |||
#include "../utility/juce_CheckSettingMacros.h" | |||
#include "../utility/juce_IncludeModuleHeaders.h" | |||
#include "../utility/juce_WindowsHooks.h" | |||
#include "../../juce_audio_processors/format_types/juce_VST3Common.h" | |||
#ifndef JUCE_VST3_CAN_REPLACE_VST2 | |||
#define JUCE_VST3_CAN_REPLACE_VST2 1 | |||
#endif | |||
#if JUCE_VST3_CAN_REPLACE_VST2 | |||
#if JUCE_MSVC | |||
#pragma warning (push) | |||
#pragma warning (disable: 4514 4996) | |||
#endif | |||
#include <pluginterfaces/vst2.x/vstfxstore.h> | |||
#if JUCE_MSVC | |||
#pragma warning (pop) | |||
#endif | |||
#endif | |||
#undef Point | |||
#undef Component | |||
@@ -194,7 +212,6 @@ public: | |||
{ | |||
valueNormalized = v; | |||
changed(); | |||
owner.setParameter (paramIndex, (float) v); | |||
return true; | |||
} | |||
@@ -526,6 +543,10 @@ private: | |||
bool isNSView; | |||
#endif | |||
#if JUCE_WINDOWS | |||
WindowsHooks hooks; | |||
#endif | |||
//============================================================================== | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVST3Editor) | |||
}; | |||
@@ -751,10 +772,39 @@ public: | |||
return kResultOk; | |||
} | |||
tresult PLUGIN_API setIoMode (Vst::IoMode) override { return kNotImplemented; } | |||
tresult PLUGIN_API getRoutingInfo (Vst::RoutingInfo&, Vst::RoutingInfo&) override { return kNotImplemented; } | |||
tresult PLUGIN_API setIoMode (Vst::IoMode) override { return kNotImplemented; } | |||
tresult PLUGIN_API getRoutingInfo (Vst::RoutingInfo&, Vst::RoutingInfo&) override { return kNotImplemented; } | |||
bool readFromMemoryStream (IBStream* state) const | |||
#if JUCE_VST3_CAN_REPLACE_VST2 | |||
void loadVST2CompatibleState (const char* data, int size) | |||
{ | |||
const int headerLen = htonl (*(juce::int32*) (data + 4)); | |||
const struct fxBank* bank = (const struct fxBank*) (data + (8 + headerLen)); | |||
const int version = htonl (bank->version); (void) version; | |||
jassert ('VstW' == htonl (*(juce::int32*) data)); | |||
jassert (1 == htonl (*(juce::int32*) (data + 8))); // version should be 1 according to Steinberg's docs | |||
jassert (cMagic == htonl (bank->chunkMagic)); | |||
jassert (chunkBankMagic == htonl (bank->fxMagic)); | |||
jassert (version == 1 || version == 2); | |||
jassert (JucePlugin_VSTUniqueID == htonl (bank->fxID)); | |||
pluginInstance->setStateInformation (bank->content.data.chunk, | |||
jmin ((int) (size - (bank->content.data.chunk - data)), | |||
(int) htonl (bank->content.data.size))); | |||
} | |||
#endif | |||
void loadStateData (const void* data, int size) | |||
{ | |||
#if JUCE_VST3_CAN_REPLACE_VST2 | |||
loadVST2CompatibleState ((const char*) data, size); | |||
#else | |||
pluginInstance->setStateInformation (data, size); | |||
#endif | |||
} | |||
bool readFromMemoryStream (IBStream* state) | |||
{ | |||
FUnknownPtr<MemoryStream> s (state); | |||
@@ -766,16 +816,16 @@ public: | |||
// Adobe Audition CS6 hack to avoid trying to use corrupted streams: | |||
if (getHostType().isAdobeAudition()) | |||
if (s->getSize() >= 5 && memcmp (s->getData(), "VC2!E", 5) == 0) | |||
return kResultFalse; | |||
return false; | |||
pluginInstance->setStateInformation (s->getData(), (int) s->getSize()); | |||
loadStateData (s->getData(), (int) s->getSize()); | |||
return true; | |||
} | |||
return false; | |||
} | |||
bool readFromUnknownStream (IBStream* state) const | |||
bool readFromUnknownStream (IBStream* state) | |||
{ | |||
MemoryOutputStream allData; | |||
@@ -785,9 +835,9 @@ public: | |||
for (;;) | |||
{ | |||
int32 bytesRead = 0; | |||
Steinberg::int32 bytesRead = 0; | |||
if (state->read (buffer, (int32) bytesPerBlock, &bytesRead) == kResultTrue && bytesRead > 0) | |||
if (state->read (buffer, (Steinberg::int32) bytesPerBlock, &bytesRead) == kResultTrue && bytesRead > 0) | |||
{ | |||
allData.write (buffer, bytesRead); | |||
continue; | |||
@@ -801,7 +851,7 @@ public: | |||
if (dataSize > 0 && dataSize < 0x7fffffff) | |||
{ | |||
pluginInstance->setStateInformation (allData.getData(), (int) dataSize); | |||
loadStateData (allData.getData(), (int) dataSize); | |||
return true; | |||
} | |||
@@ -822,16 +872,58 @@ public: | |||
return kResultFalse; | |||
} | |||
#if JUCE_VST3_CAN_REPLACE_VST2 | |||
static tresult writeVST2Int (IBStream* state, int n) | |||
{ | |||
juce::int32 t = (juce::int32) htonl (n); | |||
return state->write (&t, 4); | |||
} | |||
static tresult writeVST2Header (IBStream* state) | |||
{ | |||
tresult status = writeVST2Int (state, 'VstW'); | |||
if (status == kResultOk) status = writeVST2Int (state, 8); // header size | |||
if (status == kResultOk) status = writeVST2Int (state, 1); // version | |||
if (status == kResultOk) status = writeVST2Int (state, 0); // bypass | |||
return status; | |||
} | |||
#endif | |||
tresult PLUGIN_API getState (IBStream* state) override | |||
{ | |||
if (state != nullptr) | |||
{ | |||
juce::MemoryBlock mem; | |||
pluginInstance->getStateInformation (mem); | |||
return state->write (mem.getData(), (Steinberg::int32) mem.getSize()); | |||
} | |||
if (state == nullptr) | |||
return kInvalidArgument; | |||
return kInvalidArgument; | |||
juce::MemoryBlock mem; | |||
pluginInstance->getStateInformation (mem); | |||
#if JUCE_VST3_CAN_REPLACE_VST2 | |||
tresult status = writeVST2Header (state); | |||
if (status != kResultOk) | |||
return status; | |||
const int bankBlockSize = 160; | |||
struct fxBank bank; | |||
zerostruct (bank); | |||
bank.chunkMagic = htonl (cMagic); | |||
bank.byteSize = htonl (bankBlockSize - 8 + (unsigned int) mem.getSize()); | |||
bank.fxMagic = htonl (chunkBankMagic); | |||
bank.version = htonl (2); | |||
bank.fxID = htonl (JucePlugin_VSTUniqueID); | |||
bank.fxVersion = htonl (JucePlugin_VersionCode); | |||
bank.content.data.size = htonl ((unsigned int) mem.getSize()); | |||
status = state->write (&bank, bankBlockSize); | |||
if (status != kResultOk) | |||
return status; | |||
#endif | |||
return state->write (mem.getData(), (Steinberg::int32) mem.getSize()); | |||
} | |||
//============================================================================== | |||
@@ -1242,11 +1334,50 @@ private: | |||
DECLARE_CLASS_IID (JuceAudioProcessor, 0x0101ABAB, 0xABCDEF01, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) | |||
DEF_CLASS_IID (JuceAudioProcessor) | |||
DECLARE_CLASS_IID (JuceVST3Component, 0xABCDEF01, 0x9182FAEB, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) | |||
DEF_CLASS_IID (JuceVST3Component) | |||
#if JUCE_VST3_CAN_REPLACE_VST2 | |||
// NB: Nasty old-fashioned code in here because it's copied from the Steinberg example code. | |||
static FUID getFUIDForVST2ID (bool forControllerUID) | |||
{ | |||
char uidString[33]; | |||
const int vstfxid = (('V' << 16) | ('S' << 8) | (forControllerUID ? 'E' : 'T')); | |||
char vstfxidStr[7] = { 0 }; | |||
sprintf (vstfxidStr, "%06X", vstfxid); | |||
strcpy (uidString, vstfxidStr); | |||
char uidStr[9] = { 0 }; | |||
sprintf (uidStr, "%08X", JucePlugin_VSTUniqueID); | |||
strcat (uidString, uidStr); | |||
char nameidStr[3] = { 0 }; | |||
const size_t len = strlen (JucePlugin_Name); | |||
for (size_t i = 0; i <= 8; ++i) | |||
{ | |||
juce::uint8 c = i < len ? JucePlugin_Name[i] : 0; | |||
if (c >= 'A' && c <= 'Z') | |||
c += 'a' - 'A'; | |||
DECLARE_CLASS_IID (JuceVST3EditController, 0xABCDEF01, 0x1234ABCD, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) | |||
DEF_CLASS_IID (JuceVST3EditController) | |||
sprintf (nameidStr, "%02X", c); | |||
strcat (uidString, nameidStr); | |||
} | |||
FUID newOne; | |||
newOne.fromString (uidString); | |||
return newOne; | |||
} | |||
const Steinberg::FUID JuceVST3Component ::iid (getFUIDForVST2ID (false)); | |||
const Steinberg::FUID JuceVST3EditController::iid (getFUIDForVST2ID (true)); | |||
#else | |||
DECLARE_CLASS_IID (JuceVST3EditController, 0xABCDEF01, 0x1234ABCD, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) | |||
DEF_CLASS_IID (JuceVST3EditController) | |||
DECLARE_CLASS_IID (JuceVST3Component, 0xABCDEF01, 0x9182FAEB, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) | |||
DEF_CLASS_IID (JuceVST3Component) | |||
#endif | |||
#if JUCE_MSVC | |||
#pragma warning (pop) | |||
@@ -224,6 +224,8 @@ static void toProcessContext (Vst::ProcessContext& context, AudioPlayHead* playH | |||
} | |||
break; | |||
case AudioPlayHead::fpsUnknown: break; | |||
default: jassertfalse; break; // New frame rate? | |||
} | |||
@@ -1634,7 +1636,11 @@ public: | |||
bool hasEditor() const override | |||
{ | |||
ComSmartPtr<IPlugView> view (tryCreatingView()); //N.B.: Must use a ComSmartPtr to not delete the view from the plugin permanently! | |||
// (if possible, avoid creating a second instance of the editor, because that crashes some plugins) | |||
if (getActiveEditor() != nullptr) | |||
return true; | |||
ComSmartPtr<IPlugView> view (tryCreatingView()); | |||
return view != nullptr; | |||
} | |||
@@ -712,10 +712,9 @@ void AudioThumbnail::addBlock (const int64 startSample, const AudioSampleBuffer& | |||
for (int i = 0; i < numToDo; ++i) | |||
{ | |||
float low, high; | |||
const int start = i * samplesPerThumbSample; | |||
FloatVectorOperations::findMinAndMax (sourceData + start, jmin (samplesPerThumbSample, numSamples - start), low, high); | |||
dest[i].setFloat (low, high); | |||
Range<float> range (FloatVectorOperations::findMinAndMax (sourceData + start, jmin (samplesPerThumbSample, numSamples - start))); | |||
dest[i].setFloat (range.getStart(), range.getEnd()); | |||
} | |||
} | |||
@@ -754,7 +754,7 @@ String File::createLegalPathName (const String& original) | |||
String s (original); | |||
String start; | |||
if (s[1] == ':') | |||
if (s.isNotEmpty() && s[1] == ':') | |||
{ | |||
start = s.substring (0, 2); | |||
s = s.substring (2); | |||
@@ -34,6 +34,9 @@ | |||
/** A general-purpose range object, that simply represents any linear range with | |||
a start and end point. | |||
Note that when checking whether values fall within the range, the start value is | |||
considered to be inclusive, and the end of the range exclusive. | |||
The templated parameter is expected to be a primitive integer or floating point | |||
type, though class types could also be used if they behave in a number-like way. | |||
*/ | |||
@@ -210,7 +213,10 @@ public: | |||
return jlimit (start, end, value); | |||
} | |||
/** Returns true if the given range lies entirely inside this range. */ | |||
/** Returns true if the given range lies entirely inside this range. | |||
When making this comparison, the start value is considered to be inclusive, | |||
and the end of the range exclusive. | |||
*/ | |||
bool contains (Range other) const noexcept | |||
{ | |||
return start <= other.start && end >= other.end; | |||
@@ -237,6 +243,13 @@ public: | |||
jmax (end, other.end)); | |||
} | |||
/** Returns the smallest range that contains both this one and the given value. */ | |||
Range getUnionWith (const ValueType valueToInclude) const noexcept | |||
{ | |||
return Range (jmin (valueToInclude, start), | |||
jmax (valueToInclude, end)); | |||
} | |||
/** Returns a given range, after moving it forwards or backwards to fit it | |||
within this range. | |||
@@ -255,6 +268,26 @@ public: | |||
: rangeToConstrain.movedToStartAt (jlimit (start, end - otherLen, rangeToConstrain.getStart())); | |||
} | |||
/** Scans an array of values for its min and max, and returns these as a Range. */ | |||
static Range findMinAndMax (const ValueType* values, int numValues) noexcept | |||
{ | |||
if (numValues <= 0) | |||
return Range(); | |||
const ValueType first (*values++); | |||
Range r (first, first); | |||
while (--numValues > 0) // (> 0 rather than >= 0 because we've already taken the first sample) | |||
{ | |||
const ValueType v (*values++); | |||
if (r.end < v) r.end = v; | |||
if (v < r.start) r.start = v; | |||
} | |||
return r; | |||
} | |||
private: | |||
//============================================================================== | |||
ValueType start, end; | |||
@@ -127,8 +127,7 @@ void MemoryBlock::setSize (const size_t newSize, const bool initialiseToZero) | |||
{ | |||
if (newSize <= 0) | |||
{ | |||
data.free(); | |||
size = 0; | |||
reset(); | |||
} | |||
else | |||
{ | |||
@@ -149,6 +148,12 @@ void MemoryBlock::setSize (const size_t newSize, const bool initialiseToZero) | |||
} | |||
} | |||
void MemoryBlock::reset() | |||
{ | |||
data.free(); | |||
size = 0; | |||
} | |||
void MemoryBlock::ensureSize (const size_t minimumSize, const bool initialiseToZero) | |||
{ | |||
if (size < minimumSize) | |||
@@ -108,9 +108,9 @@ public: | |||
/** Resizes the memory block. | |||
This will try to keep as much of the block's current content as it can, | |||
and can optionally be made to clear any new space that gets allocated at | |||
the end of the block. | |||
Any data that is present in both the old and new sizes will be retained. | |||
When enlarging the block, the new space that is allocated at the end can either be | |||
cleared, or left uninitialised. | |||
@param newSize the new desired size for the block | |||
@param initialiseNewSpaceToZero if the block gets enlarged, this determines | |||
@@ -133,6 +133,9 @@ public: | |||
void ensureSize (const size_t minimumSize, | |||
bool initialiseNewSpaceToZero = false); | |||
/** Frees all the blocks data, setting its size to 0. */ | |||
void reset(); | |||
//============================================================================== | |||
/** Fills the entire memory block with a repeated byte value. | |||
This is handy for clearing a block of memory to zero. | |||
@@ -32,12 +32,7 @@ | |||
//============================================================================== | |||
/** | |||
A wrapper for a streaming (TCP) socket. | |||
This allows low-level use of sockets; for an easier-to-use messaging layer on top of | |||
sockets, you could also try the InterprocessConnection class. | |||
@see DatagramSocket, InterprocessConnection, InterprocessConnectionServer | |||
Represents a MAC network card adapter address ID. | |||
*/ | |||
class JUCE_API MACAddress | |||
{ | |||
@@ -75,8 +75,7 @@ URL::URL (const URL& other) | |||
postData (other.postData), | |||
parameterNames (other.parameterNames), | |||
parameterValues (other.parameterValues), | |||
filesToUpload (other.filesToUpload), | |||
mimeTypes (other.mimeTypes) | |||
filesToUpload (other.filesToUpload) | |||
{ | |||
} | |||
@@ -87,7 +86,6 @@ URL& URL::operator= (const URL& other) | |||
parameterNames = other.parameterNames; | |||
parameterValues = other.parameterValues; | |||
filesToUpload = other.filesToUpload; | |||
mimeTypes = other.mimeTypes; | |||
return *this; | |||
} | |||
@@ -98,8 +96,7 @@ bool URL::operator== (const URL& other) const | |||
&& postData == other.postData | |||
&& parameterNames == other.parameterNames | |||
&& parameterValues == other.parameterValues | |||
&& filesToUpload == other.filesToUpload | |||
&& mimeTypes == other.mimeTypes; | |||
&& filesToUpload == other.filesToUpload; | |||
} | |||
bool URL::operator!= (const URL& other) const | |||
@@ -156,62 +153,6 @@ namespace URLHelpers | |||
return url.indexOfChar (findStartOfNetLocation (url), '/') + 1; | |||
} | |||
static void createHeadersAndPostData (const URL& url, String& headers, MemoryBlock& postData) | |||
{ | |||
MemoryOutputStream data (postData, false); | |||
if (url.getFilesToUpload().size() > 0) | |||
{ | |||
// need to upload some files, so do it as multi-part... | |||
const String boundary (String::toHexString (Random::getSystemRandom().nextInt64())); | |||
headers << "Content-Type: multipart/form-data; boundary=" << boundary << "\r\n"; | |||
data << "--" << boundary; | |||
for (int i = 0; i < url.getParameterNames().size(); ++i) | |||
{ | |||
data << "\r\nContent-Disposition: form-data; name=\"" | |||
<< url.getParameterNames() [i] | |||
<< "\"\r\n\r\n" | |||
<< url.getParameterValues() [i] | |||
<< "\r\n--" | |||
<< boundary; | |||
} | |||
for (int i = 0; i < url.getFilesToUpload().size(); ++i) | |||
{ | |||
const File file (url.getFilesToUpload().getAllValues() [i]); | |||
const String paramName (url.getFilesToUpload().getAllKeys() [i]); | |||
data << "\r\nContent-Disposition: form-data; name=\"" << paramName | |||
<< "\"; filename=\"" << file.getFileName() << "\"\r\n"; | |||
const String mimeType (url.getMimeTypesOfUploadFiles() | |||
.getValue (paramName, String())); | |||
if (mimeType.isNotEmpty()) | |||
data << "Content-Type: " << mimeType << "\r\n"; | |||
data << "Content-Transfer-Encoding: binary\r\n\r\n" | |||
<< file << "\r\n--" << boundary; | |||
} | |||
data << "--\r\n"; | |||
} | |||
else | |||
{ | |||
data << getMangledParameters (url) | |||
<< url.getPostData(); | |||
// if the user-supplied headers didn't contain a content-type, add one now.. | |||
if (! headers.containsIgnoreCase ("Content-Type")) | |||
headers << "Content-Type: application/x-www-form-urlencoded\r\n"; | |||
headers << "Content-length: " << (int) data.getDataSize() << "\r\n"; | |||
} | |||
} | |||
static void concatenatePaths (String& path, const String& suffix) | |||
{ | |||
if (! path.endsWithChar ('/')) | |||
@@ -296,6 +237,64 @@ URL URL::getChildURL (const String& subPath) const | |||
return u; | |||
} | |||
void URL::createHeadersAndPostData (String& headers, MemoryBlock& headersAndPostData) const | |||
{ | |||
MemoryOutputStream data (headersAndPostData, false); | |||
data << URLHelpers::getMangledParameters (*this); | |||
if (filesToUpload.size() > 0) | |||
{ | |||
// (this doesn't currently support mixing custom post-data with uploads..) | |||
jassert (postData.isEmpty()); | |||
const String boundary (String::toHexString (Random::getSystemRandom().nextInt64())); | |||
headers << "Content-Type: multipart/form-data; boundary=" << boundary << "\r\n"; | |||
data << "--" << boundary; | |||
for (int i = 0; i < parameterNames.size(); ++i) | |||
{ | |||
data << "\r\nContent-Disposition: form-data; name=\"" << parameterNames[i] | |||
<< "\"\r\n\r\n" << parameterValues[i] | |||
<< "\r\n--" << boundary; | |||
} | |||
for (int i = 0; i < filesToUpload.size(); ++i) | |||
{ | |||
const Upload& f = *filesToUpload.getObjectPointerUnchecked(i); | |||
data << "\r\nContent-Disposition: form-data; name=\"" << f.parameterName | |||
<< "\"; filename=\"" << f.filename << "\"\r\n"; | |||
if (f.mimeType.isNotEmpty()) | |||
data << "Content-Type: " << f.mimeType << "\r\n"; | |||
data << "Content-Transfer-Encoding: binary\r\n\r\n"; | |||
if (f.data != nullptr) | |||
data << *f.data; | |||
else | |||
data << f.file; | |||
data << "\r\n--" << boundary; | |||
} | |||
data << "--\r\n"; | |||
} | |||
else | |||
{ | |||
data << postData; | |||
// if the user-supplied headers didn't contain a content-type, add one now.. | |||
if (! headers.containsIgnoreCase ("Content-Type")) | |||
headers << "Content-Type: application/x-www-form-urlencoded\r\n"; | |||
headers << "Content-length: " << (int) data.getDataSize() << "\r\n"; | |||
} | |||
} | |||
//============================================================================== | |||
bool URL::isProbablyAWebsiteURL (const String& possibleURL) | |||
{ | |||
@@ -339,7 +338,7 @@ InputStream* URL::createInputStream (const bool usePostCommand, | |||
headers << "\r\n"; | |||
if (usePostCommand) | |||
URLHelpers::createHeadersAndPostData (*this, headers, headersAndPostData); | |||
createHeadersAndPostData (headers, headersAndPostData); | |||
if (! headers.endsWithChar ('\n')) | |||
headers << "\r\n"; | |||
@@ -394,33 +393,44 @@ URL URL::withParameter (const String& parameterName, | |||
return u; | |||
} | |||
URL URL::withFileToUpload (const String& parameterName, | |||
const File& fileToUpload, | |||
const String& mimeType) const | |||
URL URL::withPOSTData (const String& newPostData) const | |||
{ | |||
jassert (mimeType.isNotEmpty()); // You need to supply a mime type! | |||
URL u (*this); | |||
u.filesToUpload.set (parameterName, fileToUpload.getFullPathName()); | |||
u.mimeTypes.set (parameterName, mimeType); | |||
u.postData = newPostData; | |||
return u; | |||
} | |||
URL URL::withPOSTData (const String& newPostData) const | |||
URL::Upload::Upload (const String& param, const String& name, | |||
const String& mime, const File& f, MemoryBlock* mb) | |||
: parameterName (param), filename (name), mimeType (mime), file (f), data (mb) | |||
{ | |||
jassert (mimeType.isNotEmpty()); // You need to supply a mime type! | |||
} | |||
URL URL::withUpload (Upload* const f) const | |||
{ | |||
URL u (*this); | |||
u.postData = newPostData; | |||
for (int i = u.filesToUpload.size(); --i >= 0;) | |||
if (u.filesToUpload.getObjectPointerUnchecked(i)->parameterName == f->parameterName) | |||
u.filesToUpload.remove (i); | |||
u.filesToUpload.add (f); | |||
return u; | |||
} | |||
const StringPairArray& URL::getFilesToUpload() const | |||
URL URL::withFileToUpload (const String& parameterName, const File& fileToUpload, | |||
const String& mimeType) const | |||
{ | |||
return filesToUpload; | |||
return withUpload (new Upload (parameterName, fileToUpload.getFileName(), | |||
mimeType, fileToUpload, nullptr)); | |||
} | |||
const StringPairArray& URL::getMimeTypesOfUploadFiles() const | |||
URL URL::withDataToUpload (const String& parameterName, const String& filename, | |||
const MemoryBlock& fileContentToUpload, const String& mimeType) const | |||
{ | |||
return mimeTypes; | |||
return withUpload (new Upload (parameterName, filename, mimeType, File(), | |||
new MemoryBlock (fileContentToUpload))); | |||
} | |||
//============================================================================== | |||
@@ -137,18 +137,35 @@ public: | |||
URL withParameter (const String& parameterName, | |||
const String& parameterValue) const; | |||
/** Returns a copy of this URl, with a file-upload type parameter added to it. | |||
/** Returns a copy of this URL, with a file-upload type parameter added to it. | |||
When performing a POST where one of your parameters is a binary file, this | |||
lets you specify the file. | |||
Note that the filename is stored, but the file itself won't actually be read | |||
until this URL is later used to create a network input stream. | |||
until this URL is later used to create a network input stream. If you want to | |||
upload data from memory, use withDataToUpload(). | |||
@see withDataToUpload | |||
*/ | |||
URL withFileToUpload (const String& parameterName, | |||
const File& fileToUpload, | |||
const String& mimeType) const; | |||
/** Returns a copy of this URL, with a file-upload type parameter added to it. | |||
When performing a POST where one of your parameters is a binary file, this | |||
lets you specify the file content. | |||
Note that the filename parameter should not be a full path, it's just the | |||
last part of the filename. | |||
@see withFileToUpload | |||
*/ | |||
URL withDataToUpload (const String& parameterName, | |||
const String& filename, | |||
const MemoryBlock& fileContentToUpload, | |||
const String& mimeType) const; | |||
/** Returns an array of the names of all the URL's parameters. | |||
E.g. for the url "www.fish.com?type=haddock&amount=some+fish", this array would | |||
@@ -175,17 +192,6 @@ public: | |||
*/ | |||
const StringArray& getParameterValues() const noexcept { return parameterValues; } | |||
/** Returns the set of files that should be uploaded as part of a POST operation. | |||
This is the set of files that were added to the URL with the withFileToUpload() | |||
method. | |||
*/ | |||
const StringPairArray& getFilesToUpload() const; | |||
/** Returns the set of mime types associated with each of the upload files. | |||
*/ | |||
const StringPairArray& getMimeTypesOfUploadFiles() const; | |||
/** Returns a copy of this URL, with a block of data to send as the POST data. | |||
If you're setting the POST data, be careful not to have any parameters set | |||
@@ -343,10 +349,23 @@ private: | |||
//============================================================================== | |||
String url, postData; | |||
StringArray parameterNames, parameterValues; | |||
StringPairArray filesToUpload, mimeTypes; | |||
struct Upload : public ReferenceCountedObject | |||
{ | |||
Upload (const String&, const String&, const String&, const File&, MemoryBlock*); | |||
String parameterName, filename, mimeType; | |||
File file; | |||
ScopedPointer<MemoryBlock> data; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Upload) | |||
}; | |||
ReferenceCountedArray<Upload> filesToUpload; | |||
URL (const String&, int); | |||
void addParameter (const String&, const String&); | |||
void createHeadersAndPostData (String&, MemoryBlock&) const; | |||
URL withUpload (Upload*) const; | |||
JUCE_LEAK_DETECTOR (URL) | |||
}; | |||
@@ -58,6 +58,7 @@ public: | |||
MacOSX_10_6 = 0x1006, | |||
MacOSX_10_7 = 0x1007, | |||
MacOSX_10_8 = 0x1008, | |||
MacOSX_10_9 = 0x1009, | |||
Win2000 = 0x4105, | |||
WinXP = 0x4106, | |||
@@ -105,18 +105,8 @@ void ReadWriteLock::enterWrite() const noexcept | |||
const Thread::ThreadID threadId = Thread::getCurrentThreadId(); | |||
const SpinLock::ScopedLockType sl (accessLock); | |||
for (;;) | |||
while (! tryEnterWriteInternal (threadId)) | |||
{ | |||
if (readerThreads.size() + numWriters == 0 | |||
|| threadId == writerThreadId | |||
|| (readerThreads.size() == 1 | |||
&& readerThreads.getReference(0).threadID == threadId)) | |||
{ | |||
writerThreadId = threadId; | |||
++numWriters; | |||
break; | |||
} | |||
++numWaitingWriters; | |||
accessLock.exit(); | |||
waitEvent.wait (100); | |||
@@ -127,13 +117,15 @@ void ReadWriteLock::enterWrite() const noexcept | |||
bool ReadWriteLock::tryEnterWrite() const noexcept | |||
{ | |||
const Thread::ThreadID threadId = Thread::getCurrentThreadId(); | |||
const SpinLock::ScopedLockType sl (accessLock); | |||
return tryEnterWriteInternal (Thread::getCurrentThreadId()); | |||
} | |||
bool ReadWriteLock::tryEnterWriteInternal (Thread::ThreadID threadId) const noexcept | |||
{ | |||
if (readerThreads.size() + numWriters == 0 | |||
|| threadId == writerThreadId | |||
|| (readerThreads.size() == 1 | |||
&& readerThreads.getReference(0).threadID == threadId)) | |||
|| (readerThreads.size() == 1 && readerThreads.getReference(0).threadID == threadId)) | |||
{ | |||
writerThreadId = threadId; | |||
++numWriters; | |||
@@ -143,6 +143,8 @@ private: | |||
mutable Array <ThreadRecursionCount> readerThreads; | |||
bool tryEnterWriteInternal (Thread::ThreadID) const noexcept; | |||
JUCE_DECLARE_NON_COPYABLE (ReadWriteLock) | |||
}; | |||
@@ -75,8 +75,7 @@ public: | |||
operator var() const; | |||
/** Returns the value as a string. | |||
This is alternative to writing things like "myValue.getValue().toString()". | |||
This is a shortcut for "myValue.getValue().toString()". | |||
*/ | |||
String toString() const; | |||
@@ -77,7 +77,7 @@ private: | |||
} | |||
} | |||
JUCE_DECLARE_NON_COPYABLE (ChildProcessPingThread) | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessPingThread) | |||
}; | |||
//============================================================================== | |||
@@ -128,7 +128,7 @@ public: | |||
*/ | |||
Thread::ThreadID getCurrentMessageThread() const noexcept { return messageThreadId; } | |||
/** Returns true if the caller thread has currenltly got the message manager locked. | |||
/** Returns true if the caller thread has currently got the message manager locked. | |||
see the MessageManagerLock class for more info about this. | |||
@@ -295,20 +295,12 @@ Timer::TimerThread* Timer::TimerThread::instance = nullptr; | |||
Timer::TimerThread::LockType Timer::TimerThread::lock; | |||
//============================================================================== | |||
#if JUCE_DEBUG | |||
static SortedSet <Timer*> activeTimers; | |||
#endif | |||
Timer::Timer() noexcept | |||
: countdownMs (0), | |||
periodMs (0), | |||
previous (nullptr), | |||
next (nullptr) | |||
{ | |||
#if JUCE_DEBUG | |||
const TimerThread::LockType::ScopedLockType sl (TimerThread::lock); | |||
activeTimers.add (this); | |||
#endif | |||
} | |||
Timer::Timer (const Timer&) noexcept | |||
@@ -317,30 +309,17 @@ Timer::Timer (const Timer&) noexcept | |||
previous (nullptr), | |||
next (nullptr) | |||
{ | |||
#if JUCE_DEBUG | |||
const TimerThread::LockType::ScopedLockType sl (TimerThread::lock); | |||
activeTimers.add (this); | |||
#endif | |||
} | |||
Timer::~Timer() | |||
{ | |||
stopTimer(); | |||
#if JUCE_DEBUG | |||
activeTimers.removeValue (this); | |||
#endif | |||
} | |||
void Timer::startTimer (const int interval) noexcept | |||
{ | |||
const TimerThread::LockType::ScopedLockType sl (TimerThread::lock); | |||
#if JUCE_DEBUG | |||
// this isn't a valid object! Your timer might be a dangling pointer or something.. | |||
jassert (activeTimers.contains (this)); | |||
#endif | |||
if (periodMs == 0) | |||
{ | |||
countdownMs = interval; | |||
@@ -357,11 +336,6 @@ void Timer::stopTimer() noexcept | |||
{ | |||
const TimerThread::LockType::ScopedLockType sl (TimerThread::lock); | |||
#if JUCE_DEBUG | |||
// this isn't a valid object! Your timer might be a dangling pointer or something.. | |||
jassert (activeTimers.contains (this)); | |||
#endif | |||
if (periodMs > 0) | |||
{ | |||
TimerThread::remove (this); | |||
@@ -36,6 +36,11 @@ | |||
If you want to create a copy of a native face, you can use addGlyphsFromOtherTypeface() | |||
to copy glyphs into this face. | |||
NOTE! For most people this class is almost certainly NOT the right tool to use! | |||
If what you want to do is to embed a font into your exe, then your best plan is | |||
probably to embed your TTF/OTF font file into your binary using the Introjucer, | |||
and then call Typeface::createSystemTypefaceFor() to load it from memory. | |||
@see Typeface, Font | |||
*/ | |||
class JUCE_API CustomTypeface : public Typeface | |||
@@ -108,6 +113,10 @@ public: | |||
/** Saves this typeface as a Juce-formatted font file. | |||
A CustomTypeface can be created to reload the data that is written - see the CustomTypeface | |||
constructor. | |||
NOTE! Since this class was written, support was added for loading real font files from | |||
memory, so for most people, using Typeface::createSystemTypefaceFor() to load a real font | |||
is more appropriate than using this class to store it in a proprietory format. | |||
*/ | |||
bool writeToStream (OutputStream& outputStream); | |||
@@ -254,6 +254,11 @@ Rectangle<float> Path::getBoundsTransformed (const AffineTransform& transform) c | |||
} | |||
//============================================================================== | |||
void Path::preallocateSpace (int numExtraCoordsToMakeSpaceFor) | |||
{ | |||
data.ensureAllocatedSize ((int) numElements + numExtraCoordsToMakeSpaceFor); | |||
} | |||
void Path::startNewSubPath (const float x, const float y) | |||
{ | |||
JUCE_CHECK_COORDS_ARE_VALID (x, y); | |||
@@ -263,7 +268,7 @@ void Path::startNewSubPath (const float x, const float y) | |||
else | |||
bounds.extend (x, y); | |||
data.ensureAllocatedSize ((int) numElements + 3); | |||
preallocateSpace (3); | |||
data.elements [numElements++] = moveMarker; | |||
data.elements [numElements++] = x; | |||
@@ -282,7 +287,7 @@ void Path::lineTo (const float x, const float y) | |||
if (numElements == 0) | |||
startNewSubPath (0, 0); | |||
data.ensureAllocatedSize ((int) numElements + 3); | |||
preallocateSpace (3); | |||
data.elements [numElements++] = lineMarker; | |||
data.elements [numElements++] = x; | |||
@@ -305,7 +310,7 @@ void Path::quadraticTo (const float x1, const float y1, | |||
if (numElements == 0) | |||
startNewSubPath (0, 0); | |||
data.ensureAllocatedSize ((int) numElements + 5); | |||
preallocateSpace (5); | |||
data.elements [numElements++] = quadMarker; | |||
data.elements [numElements++] = x1; | |||
@@ -334,7 +339,7 @@ void Path::cubicTo (const float x1, const float y1, | |||
if (numElements == 0) | |||
startNewSubPath (0, 0); | |||
data.ensureAllocatedSize ((int) numElements + 7); | |||
preallocateSpace (7); | |||
data.elements [numElements++] = cubicMarker; | |||
data.elements [numElements++] = x1; | |||
@@ -362,7 +367,7 @@ void Path::closeSubPath() | |||
if (numElements > 0 | |||
&& data.elements [numElements - 1] != closeSubPathMarker) | |||
{ | |||
data.ensureAllocatedSize ((int) numElements + 1); | |||
preallocateSpace (1); | |||
data.elements [numElements++] = closeSubPathMarker; | |||
} | |||
} | |||
@@ -399,7 +404,7 @@ void Path::addRectangle (const float x, const float y, | |||
if (w < 0) std::swap (x1, x2); | |||
if (h < 0) std::swap (y1, y2); | |||
data.ensureAllocatedSize ((int) numElements + 13); | |||
preallocateSpace (13); | |||
if (numElements == 0) | |||
{ | |||
@@ -560,6 +560,18 @@ public: | |||
*/ | |||
void swapWithPath (Path&) noexcept; | |||
//============================================================================== | |||
/** Preallocates enough space for adding the given number of coordinates to the path. | |||
If you're about to add a large number of lines or curves to the path, it can make | |||
the task much more efficient to call this first and avoid costly reallocations | |||
as the structure grows. | |||
The actual value to pass is a bit tricky to calculate because the space required | |||
depends on what you're adding - e.g. each lineTo() or startNewSubPath() will | |||
require 3 coords (x, y and a type marker). Each quadraticTo() will need 5, and | |||
a cubicTo() will require 7. Closing a sub-path will require 1. | |||
*/ | |||
void preallocateSpace (int numExtraCoordsToMakeSpaceFor); | |||
//============================================================================== | |||
/** Applies a 2D transform to all the vertices in the path. | |||
@@ -742,12 +754,11 @@ public: | |||
*/ | |||
void restoreFromString (StringRef stringVersion); | |||
private: | |||
//============================================================================== | |||
friend class PathFlatteningIterator; | |||
friend class Path::Iterator; | |||
ArrayAllocationBase <float, DummyCriticalSection> data; | |||
ArrayAllocationBase<float, DummyCriticalSection> data; | |||
size_t numElements; | |||
struct PathBounds | |||
@@ -164,53 +164,16 @@ public: | |||
//============================================================================== | |||
void drawGlyph (RenderTargetType& target, const Font& font, const int glyphNumber, Point<float> pos) | |||
{ | |||
++accessCounter; | |||
CachedGlyphType* glyph = nullptr; | |||
const ScopedReadLock srl (lock); | |||
for (int i = glyphs.size(); --i >= 0;) | |||
{ | |||
CachedGlyphType* const g = glyphs.getUnchecked (i); | |||
if (g->glyph == glyphNumber && g->font == font) | |||
{ | |||
glyph = g; | |||
++hits; | |||
break; | |||
} | |||
} | |||
if (glyph == nullptr) | |||
if (ReferenceCountedObjectPtr<CachedGlyphType> glyph = findOrCreateGlyph (font, glyphNumber)) | |||
{ | |||
++misses; | |||
const ScopedWriteLock swl (lock); | |||
if (hits.value + misses.value > glyphs.size() * 16) | |||
{ | |||
if (misses.value * 2 > hits.value) | |||
addNewGlyphSlots (32); | |||
hits.set (0); | |||
misses.set (0); | |||
glyph = glyphs.getLast(); | |||
} | |||
else | |||
{ | |||
glyph = findLeastRecentlyUsedGlyph(); | |||
} | |||
jassert (glyph != nullptr); | |||
glyph->generate (font, glyphNumber); | |||
glyph->lastAccessCount = ++accessCounter; | |||
glyph->draw (target, pos); | |||
} | |||
glyph->lastAccessCount = accessCounter.value; | |||
glyph->draw (target, pos); | |||
} | |||
void reset() | |||
{ | |||
const ScopedWriteLock swl (lock); | |||
const ScopedLock sl (lock); | |||
glyphs.clear(); | |||
addNewGlyphSlots (120); | |||
hits.set (0); | |||
@@ -219,9 +182,54 @@ public: | |||
private: | |||
friend struct ContainerDeletePolicy<CachedGlyphType>; | |||
OwnedArray<CachedGlyphType> glyphs; | |||
ReferenceCountedArray<CachedGlyphType> glyphs; | |||
Atomic<int> accessCounter, hits, misses; | |||
ReadWriteLock lock; | |||
CriticalSection lock; | |||
ReferenceCountedObjectPtr<CachedGlyphType> findOrCreateGlyph (const Font& font, int glyphNumber) | |||
{ | |||
const ScopedLock sl (lock); | |||
if (CachedGlyphType* g = findExistingGlyph (font, glyphNumber)) | |||
{ | |||
++hits; | |||
return g; | |||
} | |||
++misses; | |||
CachedGlyphType* g = getGlyphForReuse(); | |||
jassert (g != nullptr); | |||
g->generate (font, glyphNumber); | |||
return g; | |||
} | |||
CachedGlyphType* findExistingGlyph (const Font& font, int glyphNumber) const | |||
{ | |||
for (int i = 0; i < glyphs.size(); ++i) | |||
{ | |||
CachedGlyphType* const g = glyphs.getUnchecked (i); | |||
if (g->glyph == glyphNumber && g->font == font) | |||
return g; | |||
} | |||
return nullptr; | |||
} | |||
CachedGlyphType* getGlyphForReuse() | |||
{ | |||
if (hits.value + misses.value > glyphs.size() * 16) | |||
{ | |||
if (misses.value * 2 > hits.value) | |||
addNewGlyphSlots (32); | |||
hits.set (0); | |||
misses.set (0); | |||
return glyphs.getLast(); | |||
} | |||
return findLeastRecentlyUsedGlyph(); | |||
} | |||
void addNewGlyphSlots (int num) | |||
{ | |||
@@ -240,7 +248,8 @@ private: | |||
{ | |||
CachedGlyphType* const glyph = glyphs.getUnchecked(i); | |||
if (glyph->lastAccessCount <= oldestCounter) | |||
if (glyph->lastAccessCount <= oldestCounter | |||
&& glyph->getReferenceCount() == 1) | |||
{ | |||
oldestCounter = glyph->lastAccessCount; | |||
oldest = glyph; | |||
@@ -262,7 +271,7 @@ private: | |||
//============================================================================== | |||
/** Caches a glyph as an edge-table. */ | |||
template <class RendererType> | |||
class CachedGlyphEdgeTable | |||
class CachedGlyphEdgeTable : public ReferenceCountedObject | |||
{ | |||
public: | |||
CachedGlyphEdgeTable() : glyph (0), lastAccessCount (0) {} | |||
@@ -120,7 +120,8 @@ namespace TTFNameExtractor | |||
for (int i = 0; i < (int) ByteOrder::swapIfLittleEndian (offsetTable.numTables); ++i) | |||
{ | |||
TableDirectory tableDirectory = { 0 }; | |||
TableDirectory tableDirectory; | |||
zerostruct (tableDirectory); | |||
input.read (&tableDirectory, sizeof (tableDirectory)); | |||
if (String (tableDirectory.tag, sizeof (tableDirectory.tag)).equalsIgnoreCase ("name")) | |||
@@ -360,10 +360,12 @@ public: | |||
{ | |||
virtual ~LookAndFeelMethods() {} | |||
virtual void drawButtonBackground (Graphics&, Button& button, const Colour& backgroundColour, | |||
virtual void drawButtonBackground (Graphics&, Button&, const Colour& backgroundColour, | |||
bool isMouseOverButton, bool isButtonDown) = 0; | |||
virtual Font getTextButtonFont (TextButton& button) = 0; | |||
virtual Font getTextButtonFont (TextButton&) = 0; | |||
virtual void changeTextButtonWidthToFitText (TextButton&, int newHeight) = 0; | |||
/** Draws the text for a TextButton. */ | |||
virtual void drawButtonText (Graphics&, TextButton&, bool isMouseOverButton, bool isButtonDown) = 0; | |||
@@ -22,6 +22,10 @@ | |||
============================================================================== | |||
*/ | |||
TextButton::TextButton() : Button (String()) | |||
{ | |||
} | |||
TextButton::TextButton (const String& name, const String& toolTip) | |||
: Button (name) | |||
{ | |||
@@ -32,21 +36,15 @@ TextButton::~TextButton() | |||
{ | |||
} | |||
void TextButton::paintButton (Graphics& g, | |||
bool isMouseOverButton, | |||
bool isButtonDown) | |||
void TextButton::paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) | |||
{ | |||
LookAndFeel& lf = getLookAndFeel(); | |||
lf.drawButtonBackground (g, *this, | |||
findColour (getToggleState() ? buttonOnColourId | |||
: buttonColourId), | |||
isMouseOverButton, | |||
isButtonDown); | |||
lf.drawButtonText (g, *this, | |||
isMouseOverButton, | |||
isButtonDown); | |||
findColour (getToggleState() ? buttonOnColourId : buttonColourId), | |||
isMouseOverButton, isButtonDown); | |||
lf.drawButtonText (g, *this, isMouseOverButton, isButtonDown); | |||
} | |||
void TextButton::colourChanged() | |||
@@ -61,9 +59,5 @@ Font TextButton::getFont() | |||
void TextButton::changeWidthToFitText (const int newHeight) | |||
{ | |||
if (newHeight >= 0) | |||
setSize (jmax (1, getWidth()), newHeight); | |||
setSize (getFont().getStringWidth (getButtonText()) + getHeight(), | |||
getHeight()); | |||
getLookAndFeel().changeTextButtonWidthToFitText (*this, newHeight); | |||
} |
@@ -37,17 +37,18 @@ class JUCE_API TextButton : public Button | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Creates a TextButton. | |||
/** Creates a TextButton. */ | |||
TextButton(); | |||
/** Creates a TextButton. | |||
@param buttonName the text to put in the button (the component's name is also | |||
initially set to this string, but these can be changed later | |||
using the setName() and setButtonText() methods) | |||
@param toolTip an optional string to use as a toolip | |||
@see Button | |||
*/ | |||
TextButton (const String& buttonName = String::empty, | |||
const String& toolTip = String::empty); | |||
explicit TextButton (const String& buttonName, | |||
const String& toolTip = String::empty); | |||
/** Destructor. */ | |||
~TextButton(); | |||
@@ -74,19 +75,18 @@ public: | |||
//============================================================================== | |||
/** Resizes the button to fit neatly around its current text. | |||
If newHeight is >= 0, the button's height will be changed to this | |||
value. If it's less than zero, its height will be unaffected. | |||
*/ | |||
void changeWidthToFitText (int newHeight = -1); | |||
/** This can be overridden to use different fonts than the default one. | |||
Note that you'll need to set the font's size appropriately, too. | |||
*/ | |||
virtual Font getFont(); | |||
protected: | |||
//============================================================================== | |||
/** @internal */ | |||
void paintButton (Graphics&, bool isMouseOverButton, bool isButtonDown) override; | |||
/** @internal */ | |||
@@ -69,13 +69,15 @@ public: | |||
if (newState.width <= 0) newState.width = 100; | |||
if (newState.height <= 0) newState.height = 100; | |||
Point<float> viewboxXY; | |||
if (xml->hasAttribute ("viewBox")) | |||
{ | |||
const String viewBoxAtt (xml->getStringAttribute ("viewBox")); | |||
String::CharPointerType viewParams (viewBoxAtt.getCharPointer()); | |||
Point<float> vxy, vwh; | |||
Point<float> vwh; | |||
if (parseCoords (viewParams, vxy, true) | |||
if (parseCoords (viewParams, viewboxXY, true) | |||
&& parseCoords (viewParams, vwh, true) | |||
&& vwh.x > 0 | |||
&& vwh.y > 0) | |||
@@ -105,7 +107,7 @@ public: | |||
} | |||
newState.transform = RectanglePlacement (placementFlags) | |||
.getTransformToFit (Rectangle<float> (vxy.x, vxy.y, vwh.x, vwh.y), | |||
.getTransformToFit (Rectangle<float> (viewboxXY.x, viewboxXY.y, vwh.x, vwh.y), | |||
Rectangle<float> (newState.width, newState.height)) | |||
.followedBy (newState.transform); | |||
} | |||
@@ -118,7 +120,10 @@ public: | |||
newState.parseSubElements (xml, *drawable); | |||
drawable->setContentArea (RelativeRectangle (Rectangle<float> (newState.viewBoxW, newState.viewBoxH))); | |||
drawable->setContentArea (RelativeRectangle (RelativeCoordinate (viewboxXY.x), | |||
RelativeCoordinate (viewboxXY.x + newState.viewBoxW), | |||
RelativeCoordinate (viewboxXY.y), | |||
RelativeCoordinate (viewboxXY.y + newState.viewBoxH))); | |||
drawable->resetBoundingBoxToContentArea(); | |||
return drawable; | |||
@@ -243,6 +243,16 @@ Font LookAndFeel_V2::getTextButtonFont (TextButton& button) | |||
return button.getFont(); | |||
} | |||
void LookAndFeel_V2::changeTextButtonWidthToFitText (TextButton& b, int newHeight) | |||
{ | |||
if (newHeight >= 0) | |||
b.setSize (jmax (1, b.getWidth()), newHeight); | |||
else | |||
newHeight = b.getHeight(); | |||
b.setSize (getTextButtonFont (b).getStringWidth (b.getButtonText()) + newHeight, newHeight); | |||
} | |||
void LookAndFeel_V2::drawButtonText (Graphics& g, TextButton& button, bool /*isMouseOverButton*/, bool /*isButtonDown*/) | |||
{ | |||
Font font (getTextButtonFont (button)); | |||
@@ -46,6 +46,7 @@ public: | |||
void drawButtonText (Graphics&, TextButton& button, | |||
bool isMouseOverButton, bool isButtonDown) override; | |||
void changeTextButtonWidthToFitText (TextButton&, int newHeight) override; | |||
void drawToggleButton (Graphics&, ToggleButton& button, bool isMouseOverButton, bool isButtonDown) override; | |||
@@ -141,7 +141,7 @@ public: | |||
[window setHasShadow: ((windowStyleFlags & windowHasDropShadow) != 0)]; | |||
if (component.isAlwaysOnTop()) | |||
[window setLevel: NSFloatingWindowLevel]; | |||
setAlwaysOnTop (true); | |||
[window setContentView: view]; | |||
[window setAutodisplay: YES]; | |||
@@ -202,7 +202,9 @@ public: | |||
{ | |||
if (shouldBeVisible) | |||
{ | |||
++insideToFrontCall; | |||
[window orderFront: nil]; | |||
--insideToFrontCall; | |||
handleBroughtToFront(); | |||
} | |||
else | |||
@@ -439,8 +441,10 @@ public: | |||
bool setAlwaysOnTop (bool alwaysOnTop) override | |||
{ | |||
if (! isSharedWindow) | |||
[window setLevel: alwaysOnTop ? NSFloatingWindowLevel | |||
[window setLevel: alwaysOnTop ? ((getStyleFlags() & windowIsTemporary) != 0 ? NSPopUpMenuWindowLevel | |||
: NSFloatingWindowLevel) | |||
: NSNormalWindowLevel]; | |||
return true; | |||
} | |||
@@ -102,17 +102,38 @@ bool Desktop::canUseSemiTransparentWindows() noexcept | |||
#endif | |||
#ifndef MONITOR_DPI_TYPE | |||
enum Monitor_DPI_Type | |||
{ | |||
MDT_Effective_DPI = 0, | |||
MDT_Angular_DPI = 1, | |||
MDT_Raw_DPI = 2, | |||
MDT_Default = MDT_Effective_DPI | |||
}; | |||
enum Process_DPI_Awareness | |||
{ | |||
Process_DPI_Unaware = 0, | |||
Process_System_DPI_Aware = 1, | |||
Process_Per_Monitor_DPI_Aware = 2 | |||
}; | |||
#endif | |||
typedef BOOL (WINAPI* RegisterTouchWindowFunc) (HWND, ULONG); | |||
typedef BOOL (WINAPI* GetTouchInputInfoFunc) (HTOUCHINPUT, UINT, TOUCHINPUT*, int); | |||
typedef BOOL (WINAPI* CloseTouchInputHandleFunc) (HTOUCHINPUT); | |||
typedef BOOL (WINAPI* GetGestureInfoFunc) (HGESTUREINFO, GESTUREINFO*); | |||
typedef BOOL (WINAPI* SetProcessDPIAwareFunc)(); | |||
typedef BOOL (WINAPI* SetProcessDPIAwarenessFunc) (Process_DPI_Awareness); | |||
typedef HRESULT (WINAPI* GetDPIForMonitorFunc) (HMONITOR, Monitor_DPI_Type, UINT*, UINT*); | |||
static RegisterTouchWindowFunc registerTouchWindow = nullptr; | |||
static GetTouchInputInfoFunc getTouchInputInfo = nullptr; | |||
static CloseTouchInputHandleFunc closeTouchInputHandle = nullptr; | |||
static GetGestureInfoFunc getGestureInfo = nullptr; | |||
static SetProcessDPIAwareFunc setProcessDPIAware = nullptr; | |||
static RegisterTouchWindowFunc registerTouchWindow = nullptr; | |||
static GetTouchInputInfoFunc getTouchInputInfo = nullptr; | |||
static CloseTouchInputHandleFunc closeTouchInputHandle = nullptr; | |||
static GetGestureInfoFunc getGestureInfo = nullptr; | |||
static SetProcessDPIAwareFunc setProcessDPIAware = nullptr; | |||
static SetProcessDPIAwarenessFunc setProcessDPIAwareness = nullptr; | |||
static GetDPIForMonitorFunc getDPIForMonitor = nullptr; | |||
static bool hasCheckedForMultiTouch = false; | |||
@@ -158,17 +179,33 @@ static void setDPIAwareness() | |||
{ | |||
if (JUCEApplicationBase::isStandaloneApp()) | |||
{ | |||
if (setProcessDPIAware == nullptr) | |||
if (setProcessDPIAwareness == nullptr) | |||
{ | |||
setProcessDPIAware = (SetProcessDPIAwareFunc) getUser32Function ("SetProcessDPIAware"); | |||
HMODULE shcoreModule = GetModuleHandleA ("SHCore.dll"); | |||
if (shcoreModule != 0) | |||
{ | |||
setProcessDPIAwareness = (SetProcessDPIAwarenessFunc) GetProcAddress (shcoreModule, "SetProcessDpiAwareness"); | |||
getDPIForMonitor = (GetDPIForMonitorFunc) GetProcAddress (shcoreModule, "GetDpiForMonitor"); | |||
if (setProcessDPIAwareness != nullptr && getDPIForMonitor != nullptr | |||
// && SUCCEEDED (setProcessDPIAwareness (Process_Per_Monitor_DPI_Aware))) | |||
&& SUCCEEDED (setProcessDPIAwareness (Process_System_DPI_Aware))) // (keep using this mode temporarily..) | |||
return; | |||
} | |||
if (setProcessDPIAware == nullptr) | |||
{ | |||
setProcessDPIAware = (SetProcessDPIAwareFunc) getUser32Function ("SetProcessDPIAware"); | |||
if (setProcessDPIAware != nullptr) | |||
setProcessDPIAware(); | |||
if (setProcessDPIAware != nullptr) | |||
setProcessDPIAware(); | |||
} | |||
} | |||
} | |||
} | |||
static double getDPI() | |||
static double getGlobalDPI() | |||
{ | |||
setDPIAwareness(); | |||
@@ -181,7 +218,7 @@ static double getDPI() | |||
double Desktop::getDefaultMasterScale() | |||
{ | |||
return JUCEApplicationBase::isStandaloneApp() ? getDPI() / 96.0 | |||
return JUCEApplicationBase::isStandaloneApp() ? getGlobalDPI() / 96.0 | |||
: 1.0; | |||
} | |||
@@ -1076,6 +1113,20 @@ public: | |||
JUCE_DECLARE_NON_COPYABLE (JuceDropTarget) | |||
}; | |||
#if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client | |||
static bool offerKeyMessageToJUCEWindow (MSG& m) | |||
{ | |||
if (m.message == WM_KEYDOWN || m.message == WM_KEYUP) | |||
if (Component::getCurrentlyFocusedComponent() != nullptr) | |||
if (HWNDComponentPeer* h = getOwnerOfWindow (m.hwnd)) | |||
if (m.message == WM_KEYDOWN ? h->doKeyDown (m.wParam) | |||
: h->doKeyUp (m.wParam)) | |||
return true; | |||
return false; | |||
} | |||
#endif | |||
private: | |||
HWND hwnd, parentToAddTo; | |||
ScopedPointer<DropShadower> shadower; | |||
@@ -1987,14 +2038,20 @@ private: | |||
{ | |||
MSG msg; | |||
if (! PeekMessage (&msg, hwnd, WM_CHAR, WM_DEADCHAR, PM_NOREMOVE)) | |||
{ | |||
// if there isn't a WM_CHAR or WM_DEADCHAR message pending, we need to | |||
// manually generate the key-press event that matches this key-down. | |||
const UINT keyChar = MapVirtualKey ((UINT) key, 2); | |||
const UINT scanCode = MapVirtualKey ((UINT) key, 0); | |||
BYTE keyState[256]; | |||
GetKeyboardState (keyState); | |||
const UINT keyChar = MapVirtualKey ((UINT) key, 2); | |||
used = handleKeyPress ((int) LOWORD (keyChar), 0) || used; | |||
WCHAR text[16] = { 0 }; | |||
if (ToUnicode ((UINT) key, scanCode, keyState, text, 8, 0) != 1) | |||
text[0] = 0; | |||
used = handleKeyPress ((int) LOWORD (keyChar), (juce_wchar) text[0]) || used; | |||
} | |||
} | |||
@@ -2246,6 +2303,10 @@ private: | |||
} | |||
} | |||
void handleDPIChange() // happens when a window moves to a screen with a different DPI. | |||
{ | |||
} | |||
//============================================================================== | |||
public: | |||
static LRESULT CALLBACK windowProc (HWND h, UINT message, WPARAM wParam, LPARAM lParam) | |||
@@ -2488,6 +2549,10 @@ private: | |||
doSettingChange(); | |||
break; | |||
case 0x2e0: // WM_DPICHANGED | |||
handleDPIChange(); | |||
break; | |||
case WM_INITMENU: | |||
initialiseSysMenu ((HMENU) wParam); | |||
break; | |||
@@ -2890,6 +2955,10 @@ bool KeyPress::isKeyCurrentlyDown (const int keyCode) | |||
return HWNDComponentPeer::isKeyDown (k); | |||
} | |||
#if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client | |||
bool offerKeyMessageToJUCEWindow (MSG& m) { return HWNDComponentPeer::offerKeyMessageToJUCEWindow (m); } | |||
#endif | |||
//============================================================================== | |||
bool JUCE_CALLTYPE Process::isForegroundProcess() | |||
{ | |||
@@ -3183,9 +3252,11 @@ void Desktop::setKioskComponent (Component* kioskModeComponent, bool enableOrDis | |||
//============================================================================== | |||
struct MonitorInfo | |||
{ | |||
MonitorInfo (Rectangle<int> rect, bool main) noexcept : isMain (main), bounds (rect) {} | |||
MonitorInfo (Rectangle<int> rect, bool main, double d) noexcept | |||
: bounds (rect), dpi (d), isMain (main) {} | |||
Rectangle<int> bounds; | |||
double dpi; | |||
bool isMain; | |||
}; | |||
@@ -3195,7 +3266,17 @@ static BOOL CALLBACK enumMonitorsProc (HMONITOR hm, HDC, LPRECT r, LPARAM userIn | |||
info.cbSize = sizeof (info); | |||
GetMonitorInfo (hm, &info); | |||
const bool isMain = (info.dwFlags & 1 /* MONITORINFOF_PRIMARY */) != 0; | |||
((Array<MonitorInfo>*) userInfo)->add (MonitorInfo (rectangleFromRECT (*r), isMain)); | |||
double dpi = 0; | |||
if (getDPIForMonitor != nullptr) | |||
{ | |||
UINT dpiX = 0, dpiY = 0; | |||
if (SUCCEEDED (getDPIForMonitor (hm, MDT_Default, &dpiX, &dpiY))) | |||
dpi = (dpiX + dpiY) / 2.0; | |||
} | |||
((Array<MonitorInfo>*) userInfo)->add (MonitorInfo (rectangleFromRECT (*r), isMain, dpi)); | |||
return TRUE; | |||
} | |||
@@ -3207,8 +3288,10 @@ void Desktop::Displays::findDisplays (float masterScale) | |||
Array<MonitorInfo> monitors; | |||
EnumDisplayMonitors (0, 0, &enumMonitorsProc, (LPARAM) &monitors); | |||
const double globalDPI = getGlobalDPI(); | |||
if (monitors.size() == 0) | |||
monitors.add (MonitorInfo (rectangleFromRECT (getWindowRect (GetDesktopWindow())), true)); | |||
monitors.add (MonitorInfo (rectangleFromRECT (getWindowRect (GetDesktopWindow())), true, globalDPI)); | |||
// make sure the first in the list is the main monitor | |||
for (int i = 1; i < monitors.size(); ++i) | |||
@@ -3218,15 +3301,22 @@ void Desktop::Displays::findDisplays (float masterScale) | |||
RECT workArea; | |||
SystemParametersInfo (SPI_GETWORKAREA, 0, &workArea, 0); | |||
const double dpi = getDPI(); // (this has only one value for all monitors) | |||
for (int i = 0; i < monitors.size(); ++i) | |||
{ | |||
Display d; | |||
d.userArea = d.totalArea = monitors.getReference(i).bounds / masterScale; | |||
d.isMain = monitors.getReference(i).isMain; | |||
d.scale = masterScale; | |||
d.dpi = dpi; | |||
d.dpi = monitors.getReference(i).dpi; | |||
if (d.dpi == 0) | |||
{ | |||
d.scale = masterScale; | |||
d.dpi = globalDPI; | |||
} | |||
else | |||
{ | |||
d.scale = d.dpi / 96.0; | |||
} | |||
if (d.isMain) | |||
d.userArea = d.userArea.getIntersection (rectangleFromRECT (workArea) / masterScale); | |||
@@ -892,7 +892,7 @@ public: | |||
if (isAbsoluteDragMode (e.mods) || (maximum - minimum) / sliderRegionSize < interval) | |||
{ | |||
dragMode = notDragging; | |||
dragMode = absoluteDrag; | |||
handleAbsoluteDrag (e); | |||
} | |||
else | |||
@@ -23,7 +23,7 @@ | |||
*/ | |||
CallOutBox::CallOutBox (Component& c, const Rectangle<int>& area, Component* const parent) | |||
: borderSpace (20), arrowSize (16.0f), content (c) | |||
: arrowSize (16.0f), content (c) | |||
{ | |||
addAndMakeVisible (content); | |||
@@ -86,10 +86,14 @@ CallOutBox& CallOutBox::launchAsynchronously (Component* content, const Rectangl | |||
void CallOutBox::setArrowSize (const float newSize) | |||
{ | |||
arrowSize = newSize; | |||
borderSpace = jmax (20, (int) arrowSize); | |||
refreshPath(); | |||
} | |||
int CallOutBox::getBorderSize() const noexcept | |||
{ | |||
return jmax (20, (int) arrowSize); | |||
} | |||
void CallOutBox::paint (Graphics& g) | |||
{ | |||
getLookAndFeel().drawCallOutBoxBackground (*this, g, outline, background); | |||
@@ -97,6 +101,7 @@ void CallOutBox::paint (Graphics& g) | |||
void CallOutBox::resized() | |||
{ | |||
const int borderSpace = getBorderSize(); | |||
content.setTopLeftPosition (borderSpace, borderSpace); | |||
refreshPath(); | |||
} | |||
@@ -168,6 +173,8 @@ void CallOutBox::updatePosition (const Rectangle<int>& newAreaToPointTo, const R | |||
targetArea = newAreaToPointTo; | |||
availableArea = newAreaToFitIn; | |||
const int borderSpace = getBorderSize(); | |||
Rectangle<int> newBounds (content.getWidth() + borderSpace * 2, | |||
content.getHeight() + borderSpace * 2); | |||
@@ -149,10 +149,11 @@ public: | |||
bool keyPressed (const KeyPress&) override; | |||
/** @internal */ | |||
void handleCommandMessage (int) override; | |||
/** @internal */ | |||
int getBorderSize() const noexcept; | |||
private: | |||
//============================================================================== | |||
int borderSpace; | |||
float arrowSize; | |||
Component& content; | |||
Path outline; | |||
@@ -41,17 +41,17 @@ void WebBrowserComponent::goToURL (const String& url, | |||
{ | |||
lastURL = url; | |||
lastHeaders.clear(); | |||
if (headers != nullptr) | |||
lastHeaders = *headers; | |||
else | |||
lastHeaders.clear(); | |||
lastPostData.setSize (0); | |||
if (postData != nullptr) | |||
lastPostData = *postData; | |||
else | |||
lastPostData.reset(); | |||
blankPageShown = false; | |||
} | |||
void WebBrowserComponent::stop() | |||
@@ -46,17 +46,17 @@ void WebBrowserComponent::goToURL (const String& url, | |||
{ | |||
lastURL = url; | |||
lastHeaders.clear(); | |||
if (headers != nullptr) | |||
lastHeaders = *headers; | |||
else | |||
lastHeaders.clear(); | |||
lastPostData.setSize (0); | |||
if (postData != nullptr) | |||
lastPostData = *postData; | |||
else | |||
lastPostData.reset(); | |||
blankPageShown = false; | |||
} | |||
void WebBrowserComponent::stop() | |||
@@ -265,13 +265,15 @@ void WebBrowserComponent::goToURL (const String& url, | |||
{ | |||
lastURL = url; | |||
lastHeaders.clear(); | |||
if (headers != nullptr) | |||
lastHeaders = *headers; | |||
else | |||
lastHeaders.clear(); | |||
lastPostData.setSize (0); | |||
if (postData != nullptr) | |||
lastPostData = *postData; | |||
else | |||
lastPostData.reset(); | |||
blankPageShown = false; | |||
@@ -214,13 +214,15 @@ void WebBrowserComponent::goToURL (const String& url, | |||
{ | |||
lastURL = url; | |||
lastHeaders.clear(); | |||
if (headers != nullptr) | |||
lastHeaders = *headers; | |||
else | |||
lastHeaders.clear(); | |||
lastPostData.setSize (0); | |||
if (postData != nullptr) | |||
lastPostData = *postData; | |||
else | |||
lastPostData.reset(); | |||
blankPageShown = false; | |||