| @@ -471,7 +471,7 @@ private: | |||
| snd_midi_event_t* midiParser; | |||
| int maxEventSize; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutputDevice); | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutputDevice) | |||
| }; | |||
| } // namespace | |||
| @@ -695,7 +695,7 @@ private: | |||
| win[0][i + 18] = win[3][i + 18] = (float) (0.5 * std::sin (double_Pi / 72.0 * (2 * (i + 18) + 1)) / std::cos (double_Pi * (2 * (i + 18) + 19) / 72.0)); | |||
| } | |||
| const double piOver72 = double_Pi; | |||
| const double piOver72 = double_Pi / 72.0; | |||
| for (i = 0; i < 6; ++i) | |||
| { | |||
| @@ -36,7 +36,7 @@ const char* const WavAudioFormat::bwavCodingHistory = "bwav coding history"; | |||
| StringPairArray WavAudioFormat::createBWAVMetadata (const String& description, | |||
| const String& originator, | |||
| const String& originatorRef, | |||
| const Time date, | |||
| Time date, | |||
| const int64 timeReferenceSamples, | |||
| const String& codingHistory) | |||
| { | |||
| @@ -64,6 +64,85 @@ const char* const WavAudioFormat::acidDenominator = "acid denominator"; | |||
| const char* const WavAudioFormat::acidNumerator = "acid numerator"; | |||
| const char* const WavAudioFormat::acidTempo = "acid tempo"; | |||
| const char* const WavAudioFormat::riffInfoArchivalLocation = "IARL"; | |||
| const char* const WavAudioFormat::riffInfoArtist = "IART"; | |||
| const char* const WavAudioFormat::riffInfoBaseURL = "IBSU"; | |||
| const char* const WavAudioFormat::riffInfoCinematographer = "ICNM"; | |||
| const char* const WavAudioFormat::riffInfoComment = "CMNT"; | |||
| const char* const WavAudioFormat::riffInfoComments = "COMM"; | |||
| const char* const WavAudioFormat::riffInfoCommissioned = "ICMS"; | |||
| const char* const WavAudioFormat::riffInfoCopyright = "ICOP"; | |||
| const char* const WavAudioFormat::riffInfoCostumeDesigner = "ICDS"; | |||
| const char* const WavAudioFormat::riffInfoCountry = "ICNT"; | |||
| const char* const WavAudioFormat::riffInfoCropped = "ICRP"; | |||
| const char* const WavAudioFormat::riffInfoDateCreated = "ICRD"; | |||
| const char* const WavAudioFormat::riffInfoDateTimeOriginal = "IDIT"; | |||
| const char* const WavAudioFormat::riffInfoDefaultAudioStream = "ICAS"; | |||
| const char* const WavAudioFormat::riffInfoDimension = "IDIM"; | |||
| const char* const WavAudioFormat::riffInfoDirectory = "DIRC"; | |||
| const char* const WavAudioFormat::riffInfoDistributedBy = "IDST"; | |||
| const char* const WavAudioFormat::riffInfoDotsPerInch = "IDPI"; | |||
| const char* const WavAudioFormat::riffInfoEditedBy = "IEDT"; | |||
| const char* const WavAudioFormat::riffInfoEighthLanguage = "IAS8"; | |||
| const char* const WavAudioFormat::riffInfoEncodedBy = "CODE"; | |||
| const char* const WavAudioFormat::riffInfoEndTimecode = "TCDO"; | |||
| const char* const WavAudioFormat::riffInfoEngineer = "IENG"; | |||
| const char* const WavAudioFormat::riffInfoFifthLanguage = "IAS5"; | |||
| const char* const WavAudioFormat::riffInfoFirstLanguage = "IAS1"; | |||
| const char* const WavAudioFormat::riffInfoFourthLanguage = "IAS4"; | |||
| const char* const WavAudioFormat::riffInfoGenre = "GENR"; | |||
| const char* const WavAudioFormat::riffInfoKeywords = "IKEY"; | |||
| const char* const WavAudioFormat::riffInfoLanguage = "LANG"; | |||
| const char* const WavAudioFormat::riffInfoLength = "TLEN"; | |||
| const char* const WavAudioFormat::riffInfoLightness = "ILGT"; | |||
| const char* const WavAudioFormat::riffInfoLocation = "LOCA"; | |||
| const char* const WavAudioFormat::riffInfoLogoIconURL = "ILIU"; | |||
| const char* const WavAudioFormat::riffInfoLogoURL = "ILGU"; | |||
| const char* const WavAudioFormat::riffInfoMedium = "IMED"; | |||
| const char* const WavAudioFormat::riffInfoMoreInfoBannerImage = "IMBI"; | |||
| const char* const WavAudioFormat::riffInfoMoreInfoBannerURL = "IMBU"; | |||
| const char* const WavAudioFormat::riffInfoMoreInfoText = "IMIT"; | |||
| const char* const WavAudioFormat::riffInfoMoreInfoURL = "IMIU"; | |||
| const char* const WavAudioFormat::riffInfoMusicBy = "IMUS"; | |||
| const char* const WavAudioFormat::riffInfoNinthLanguage = "IAS9"; | |||
| const char* const WavAudioFormat::riffInfoNumberOfParts = "PRT2"; | |||
| const char* const WavAudioFormat::riffInfoOrganisation = "TORG"; | |||
| const char* const WavAudioFormat::riffInfoPart = "PRT1"; | |||
| const char* const WavAudioFormat::riffInfoProducedBy = "IPRO"; | |||
| const char* const WavAudioFormat::riffInfoProductionDesigner = "IPDS"; | |||
| const char* const WavAudioFormat::riffInfoProductionStudio = "ISDT"; | |||
| const char* const WavAudioFormat::riffInfoRate = "RATE"; | |||
| const char* const WavAudioFormat::riffInfoRated = "AGES"; | |||
| const char* const WavAudioFormat::riffInfoRating = "IRTD"; | |||
| const char* const WavAudioFormat::riffInfoRippedBy = "IRIP"; | |||
| const char* const WavAudioFormat::riffInfoSecondaryGenre = "ISGN"; | |||
| const char* const WavAudioFormat::riffInfoSecondLanguage = "IAS2"; | |||
| const char* const WavAudioFormat::riffInfoSeventhLanguage = "IAS7"; | |||
| const char* const WavAudioFormat::riffInfoSharpness = "ISHP"; | |||
| const char* const WavAudioFormat::riffInfoSixthLanguage = "IAS6"; | |||
| const char* const WavAudioFormat::riffInfoSoftware = "ISFT"; | |||
| const char* const WavAudioFormat::riffInfoSoundSchemeTitle = "DISP"; | |||
| const char* const WavAudioFormat::riffInfoSource = "ISRC"; | |||
| const char* const WavAudioFormat::riffInfoSourceFrom = "ISRF"; | |||
| const char* const WavAudioFormat::riffInfoStarring_ISTR = "ISTR"; | |||
| const char* const WavAudioFormat::riffInfoStarring_STAR = "STAR"; | |||
| const char* const WavAudioFormat::riffInfoStartTimecode = "TCOD"; | |||
| const char* const WavAudioFormat::riffInfoStatistics = "STAT"; | |||
| const char* const WavAudioFormat::riffInfoSubject = "ISBJ"; | |||
| const char* const WavAudioFormat::riffInfoTapeName = "TAPE"; | |||
| const char* const WavAudioFormat::riffInfoTechnician = "ITCH"; | |||
| const char* const WavAudioFormat::riffInfoThirdLanguage = "IAS3"; | |||
| const char* const WavAudioFormat::riffInfoTimeCode = "ISMP"; | |||
| const char* const WavAudioFormat::riffInfoTitle = "INAM"; | |||
| const char* const WavAudioFormat::riffInfoTrackNumber = "TRCK"; | |||
| const char* const WavAudioFormat::riffInfoURL = "TURL"; | |||
| const char* const WavAudioFormat::riffInfoVegasVersionMajor = "VMAJ"; | |||
| const char* const WavAudioFormat::riffInfoVegasVersionMinor = "VMIN"; | |||
| const char* const WavAudioFormat::riffInfoVersion = "TVER"; | |||
| const char* const WavAudioFormat::riffInfoWatermarkURL = "IWMU"; | |||
| const char* const WavAudioFormat::riffInfoWrittenBy = "IWRI"; | |||
| const char* const WavAudioFormat::riffInfoYear = "YEAR"; | |||
| const char* const WavAudioFormat::ISRC = "ISRC"; | |||
| const char* const WavAudioFormat::tracktionLoopInfo = "tracktion loop info"; | |||
| @@ -101,7 +180,7 @@ namespace WavFileHelpers | |||
| const uint32 timeLow = ByteOrder::swapIfBigEndian (timeRefLow); | |||
| const uint32 timeHigh = ByteOrder::swapIfBigEndian (timeRefHigh); | |||
| const int64 time = (((int64)timeHigh) << 32) + timeLow; | |||
| const int64 time = (((int64) timeHigh) << 32) + timeLow; | |||
| values.set (WavAudioFormat::bwavTimeReference, String (time)); | |||
| values.set (WavAudioFormat::bwavCodingHistory, | |||
| @@ -473,8 +552,120 @@ namespace WavFileHelpers | |||
| } | |||
| //============================================================================== | |||
| /** Reads a RIFF List Info chunk from a stream positioned just after the size byte. */ | |||
| namespace ListInfoChunk | |||
| { | |||
| static const char* const types[] = | |||
| { | |||
| WavAudioFormat::riffInfoArchivalLocation, | |||
| WavAudioFormat::riffInfoArtist, | |||
| WavAudioFormat::riffInfoBaseURL, | |||
| WavAudioFormat::riffInfoCinematographer, | |||
| WavAudioFormat::riffInfoComment, | |||
| WavAudioFormat::riffInfoComments, | |||
| WavAudioFormat::riffInfoCommissioned, | |||
| WavAudioFormat::riffInfoCopyright, | |||
| WavAudioFormat::riffInfoCostumeDesigner, | |||
| WavAudioFormat::riffInfoCountry, | |||
| WavAudioFormat::riffInfoCropped, | |||
| WavAudioFormat::riffInfoDateCreated, | |||
| WavAudioFormat::riffInfoDateTimeOriginal, | |||
| WavAudioFormat::riffInfoDefaultAudioStream, | |||
| WavAudioFormat::riffInfoDimension, | |||
| WavAudioFormat::riffInfoDirectory, | |||
| WavAudioFormat::riffInfoDistributedBy, | |||
| WavAudioFormat::riffInfoDotsPerInch, | |||
| WavAudioFormat::riffInfoEditedBy, | |||
| WavAudioFormat::riffInfoEighthLanguage, | |||
| WavAudioFormat::riffInfoEncodedBy, | |||
| WavAudioFormat::riffInfoEndTimecode, | |||
| WavAudioFormat::riffInfoEngineer, | |||
| WavAudioFormat::riffInfoFifthLanguage, | |||
| WavAudioFormat::riffInfoFirstLanguage, | |||
| WavAudioFormat::riffInfoFourthLanguage, | |||
| WavAudioFormat::riffInfoGenre, | |||
| WavAudioFormat::riffInfoKeywords, | |||
| WavAudioFormat::riffInfoLanguage, | |||
| WavAudioFormat::riffInfoLength, | |||
| WavAudioFormat::riffInfoLightness, | |||
| WavAudioFormat::riffInfoLocation, | |||
| WavAudioFormat::riffInfoLogoIconURL, | |||
| WavAudioFormat::riffInfoLogoURL, | |||
| WavAudioFormat::riffInfoMedium, | |||
| WavAudioFormat::riffInfoMoreInfoBannerImage, | |||
| WavAudioFormat::riffInfoMoreInfoBannerURL, | |||
| WavAudioFormat::riffInfoMoreInfoText, | |||
| WavAudioFormat::riffInfoMoreInfoURL, | |||
| WavAudioFormat::riffInfoMusicBy, | |||
| WavAudioFormat::riffInfoNinthLanguage, | |||
| WavAudioFormat::riffInfoNumberOfParts, | |||
| WavAudioFormat::riffInfoOrganisation, | |||
| WavAudioFormat::riffInfoPart, | |||
| WavAudioFormat::riffInfoProducedBy, | |||
| WavAudioFormat::riffInfoProductionDesigner, | |||
| WavAudioFormat::riffInfoProductionStudio, | |||
| WavAudioFormat::riffInfoRate, | |||
| WavAudioFormat::riffInfoRated, | |||
| WavAudioFormat::riffInfoRating, | |||
| WavAudioFormat::riffInfoRippedBy, | |||
| WavAudioFormat::riffInfoSecondaryGenre, | |||
| WavAudioFormat::riffInfoSecondLanguage, | |||
| WavAudioFormat::riffInfoSeventhLanguage, | |||
| WavAudioFormat::riffInfoSharpness, | |||
| WavAudioFormat::riffInfoSixthLanguage, | |||
| WavAudioFormat::riffInfoSoftware, | |||
| WavAudioFormat::riffInfoSoundSchemeTitle, | |||
| WavAudioFormat::riffInfoSource, | |||
| WavAudioFormat::riffInfoSourceFrom, | |||
| WavAudioFormat::riffInfoStarring_ISTR, | |||
| WavAudioFormat::riffInfoStarring_STAR, | |||
| WavAudioFormat::riffInfoStartTimecode, | |||
| WavAudioFormat::riffInfoStatistics, | |||
| WavAudioFormat::riffInfoSubject, | |||
| WavAudioFormat::riffInfoTapeName, | |||
| WavAudioFormat::riffInfoTechnician, | |||
| WavAudioFormat::riffInfoThirdLanguage, | |||
| WavAudioFormat::riffInfoTimeCode, | |||
| WavAudioFormat::riffInfoTitle, | |||
| WavAudioFormat::riffInfoTrackNumber, | |||
| WavAudioFormat::riffInfoURL, | |||
| WavAudioFormat::riffInfoVegasVersionMajor, | |||
| WavAudioFormat::riffInfoVegasVersionMinor, | |||
| WavAudioFormat::riffInfoVersion, | |||
| WavAudioFormat::riffInfoWatermarkURL, | |||
| WavAudioFormat::riffInfoWrittenBy, | |||
| WavAudioFormat::riffInfoYear | |||
| }; | |||
| static bool isMatchingTypeIgnoringCase (const int value, const char* const name) noexcept | |||
| { | |||
| for (int i = 0; i < 4; ++i) | |||
| if ((juce_wchar) name[i] != CharacterFunctions::toUpperCase ((juce_wchar) ((value >> (i * 8)) & 0xff))) | |||
| return false; | |||
| return true; | |||
| } | |||
| static void addToMetadata (StringPairArray& values, InputStream& input, int64 chunkEnd) | |||
| { | |||
| while (input.getPosition() < chunkEnd) | |||
| { | |||
| const int infoType = input.readInt(); | |||
| const int64 infoLength = jlimit ((int64) 0, chunkEnd - input.getPosition(), (int64) input.readInt()); | |||
| for (int i = 0; i < numElementsInArray (types); ++i) | |||
| { | |||
| if (isMatchingTypeIgnoringCase (infoType, types[i])) | |||
| { | |||
| MemoryBlock mb; | |||
| input.readIntoMemoryBlock (mb, (ssize_t) infoLength); | |||
| values.set (types[i], mb.toString()); | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| static bool writeValue (const StringPairArray& values, MemoryOutputStream& out, const char* paramName) | |||
| { | |||
| const String value (values.getValue (paramName, String())); | |||
| @@ -497,15 +688,12 @@ namespace WavFileHelpers | |||
| static MemoryBlock createFrom (const StringPairArray& values) | |||
| { | |||
| static const char* params[] = { "INAM", "IART", "IPRD", "IPRT", "ISFT", | |||
| "ISRC", "IGNR", "ICMT", "ICOP", "ICRD" }; | |||
| MemoryOutputStream out; | |||
| out.writeInt (chunkName ("INFO")); | |||
| bool anyParamsDefined = false; | |||
| for (int i = 0; i < numElementsInArray (params); ++i) | |||
| if (writeValue (values, out, params[i])) | |||
| for (int i = 0; i < numElementsInArray (types); ++i) | |||
| if (writeValue (values, out, types[i])) | |||
| anyParamsDefined = true; | |||
| return anyParamsDefined ? out.getMemoryBlock() : MemoryBlock(); | |||
| @@ -833,28 +1021,28 @@ public: | |||
| bwavChunkStart = input->getPosition(); | |||
| bwavSize = length; | |||
| HeapBlock <BWAVChunk> bwav; | |||
| HeapBlock<BWAVChunk> bwav; | |||
| bwav.calloc (jmax ((size_t) length + 1, sizeof (BWAVChunk)), 1); | |||
| input->read (bwav, (int) length); | |||
| bwav->copyTo (metadataValues, (int) length); | |||
| } | |||
| else if (chunkType == chunkName ("smpl")) | |||
| { | |||
| HeapBlock <SMPLChunk> smpl; | |||
| HeapBlock<SMPLChunk> smpl; | |||
| smpl.calloc (jmax ((size_t) length + 1, sizeof (SMPLChunk)), 1); | |||
| input->read (smpl, (int) length); | |||
| smpl->copyTo (metadataValues, (int) length); | |||
| } | |||
| else if (chunkType == chunkName ("inst") || chunkType == chunkName ("INST")) // need to check which... | |||
| { | |||
| HeapBlock <InstChunk> inst; | |||
| HeapBlock<InstChunk> inst; | |||
| inst.calloc (jmax ((size_t) length + 1, sizeof (InstChunk)), 1); | |||
| input->read (inst, (int) length); | |||
| inst->copyTo (metadataValues); | |||
| } | |||
| else if (chunkType == chunkName ("cue ")) | |||
| { | |||
| HeapBlock <CueChunk> cue; | |||
| HeapBlock<CueChunk> cue; | |||
| cue.calloc (jmax ((size_t) length + 1, sizeof (CueChunk)), 1); | |||
| input->read (cue, (int) length); | |||
| cue->copyTo (metadataValues, (int) length); | |||
| @@ -867,7 +1055,13 @@ public: | |||
| } | |||
| else if (chunkType == chunkName ("LIST")) | |||
| { | |||
| if (input->readInt() == chunkName ("adtl")) | |||
| const int subChunkType = input->readInt(); | |||
| if (subChunkType == chunkName ("info") || subChunkType == chunkName ("INFO")) | |||
| { | |||
| ListInfoChunk::addToMetadata (metadataValues, *input, chunkEnd); | |||
| } | |||
| else if (subChunkType == chunkName ("adtl")) | |||
| { | |||
| while (input->getPosition() < chunkEnd) | |||
| { | |||
| @@ -1495,3 +1689,70 @@ bool WavAudioFormat::replaceMetadataInFile (const File& wavFile, const StringPai | |||
| return slowCopyWavFileWithNewMetadata (wavFile, newMetadata); | |||
| } | |||
| //============================================================================== | |||
| #if JUCE_UNIT_TESTS | |||
| class WaveAudioFormatTests : public UnitTest | |||
| { | |||
| public: | |||
| WaveAudioFormatTests() : UnitTest ("Wave audio format tests") {} | |||
| void runTest() override | |||
| { | |||
| beginTest ("Setting up metadata"); | |||
| StringPairArray metadataValues = WavAudioFormat::createBWAVMetadata ("description", | |||
| "originator", | |||
| "originatorRef", | |||
| Time::getCurrentTime(), | |||
| numTestAudioBufferSamples, | |||
| "codingHistory"); | |||
| for (int i = numElementsInArray (WavFileHelpers::ListInfoChunk::types); --i >= 0;) | |||
| metadataValues.set (WavFileHelpers::ListInfoChunk::types[i], | |||
| WavFileHelpers::ListInfoChunk::types[i]); | |||
| if (metadataValues.size() > 0) | |||
| metadataValues.set ("MetaDataSource", "WAV"); | |||
| WavAudioFormat format; | |||
| MemoryBlock memoryBlock; | |||
| { | |||
| beginTest ("Creating a basic wave writer"); | |||
| ScopedPointer<AudioFormatWriter> writer (format.createWriterFor (new MemoryOutputStream (memoryBlock, false), | |||
| 44100.0, numTestAudioBufferChannels, | |||
| 32, metadataValues, 0)); | |||
| expect (writer != nullptr); | |||
| AudioSampleBuffer buffer (numTestAudioBufferChannels, numTestAudioBufferSamples); | |||
| buffer.clear(); | |||
| beginTest ("Writing audio data to the basic wave writer"); | |||
| expect (writer->writeFromAudioSampleBuffer (buffer, 0, numTestAudioBufferSamples)); | |||
| } | |||
| { | |||
| beginTest ("Creating a basic wave reader"); | |||
| ScopedPointer<AudioFormatReader> reader (format.createReaderFor (new MemoryInputStream (memoryBlock, false), false)); | |||
| expect (reader != nullptr); | |||
| expect (reader->metadataValues == metadataValues, "Somehow, the metadata is different!"); | |||
| } | |||
| } | |||
| private: | |||
| enum | |||
| { | |||
| numTestAudioBufferChannels = 2, | |||
| numTestAudioBufferSamples = 256 | |||
| }; | |||
| JUCE_DECLARE_NON_COPYABLE (WaveAudioFormatTests) | |||
| }; | |||
| static const WaveAudioFormatTests waveAudioFormatTests; | |||
| #endif | |||
| @@ -39,48 +39,16 @@ public: | |||
| ~WavAudioFormat(); | |||
| //============================================================================== | |||
| /** Metadata property name used by wav readers and writers for adding | |||
| a BWAV chunk to the file. | |||
| // BWAV chunk properties: | |||
| @see AudioFormatReader::metadataValues, createWriterFor | |||
| */ | |||
| static const char* const bwavDescription; | |||
| /** Metadata property name used by wav readers and writers for adding | |||
| a BWAV chunk to the file. | |||
| @see AudioFormatReader::metadataValues, createWriterFor | |||
| */ | |||
| static const char* const bwavOriginator; | |||
| /** Metadata property name used by wav readers and writers for adding | |||
| a BWAV chunk to the file. | |||
| @see AudioFormatReader::metadataValues, createWriterFor | |||
| */ | |||
| static const char* const bwavOriginatorRef; | |||
| /** Metadata property name used by wav readers and writers for adding | |||
| a BWAV chunk to the file. | |||
| Date format is: yyyy-mm-dd | |||
| @see AudioFormatReader::metadataValues, createWriterFor | |||
| */ | |||
| static const char* const bwavOriginationDate; | |||
| /** Metadata property name used by wav readers and writers for adding | |||
| a BWAV chunk to the file. | |||
| Time format is: hh-mm-ss | |||
| @see AudioFormatReader::metadataValues, createWriterFor | |||
| */ | |||
| static const char* const bwavOriginationTime; | |||
| /** Metadata property name used by wav readers and writers for adding | |||
| a BWAV chunk to the file. | |||
| static const char* const bwavDescription; /**< Metadata property name used in BWAV chunks. */ | |||
| static const char* const bwavOriginator; /**< Metadata property name used in BWAV chunks. */ | |||
| static const char* const bwavOriginatorRef; /**< Metadata property name used in BWAV chunks. */ | |||
| static const char* const bwavOriginationDate; /**< Metadata property name used in BWAV chunks. The format should be: yyyy-mm-dd */ | |||
| static const char* const bwavOriginationTime; /**< Metadata property name used in BWAV chunks. The format should be: format is: hh-mm-ss */ | |||
| static const char* const bwavCodingHistory; /**< Metadata property name used in BWAV chunks. */ | |||
| /** Metadata property name used in BWAV chunks. | |||
| This is the number of samples from the start of an edit that the | |||
| file is supposed to begin at. Seems like an obvious mistake to | |||
| only allow a file to occur in an edit once, but that's the way | |||
| @@ -90,13 +58,6 @@ public: | |||
| */ | |||
| static const char* const bwavTimeReference; | |||
| /** Metadata property name used by wav readers and writers for adding | |||
| a BWAV chunk to the file. | |||
| @see AudioFormatReader::metadataValues, createWriterFor | |||
| */ | |||
| static const char* const bwavCodingHistory; | |||
| /** Utility function to fill out the appropriate metadata for a BWAV file. | |||
| This just makes it easier than using the property names directly, and it | |||
| @@ -105,31 +66,105 @@ public: | |||
| static StringPairArray createBWAVMetadata (const String& description, | |||
| const String& originator, | |||
| const String& originatorRef, | |||
| const Time dateAndTime, | |||
| const int64 timeReferenceSamples, | |||
| Time dateAndTime, | |||
| int64 timeReferenceSamples, | |||
| const String& codingHistory); | |||
| //============================================================================== | |||
| /** Metadata property name used when reading a WAV file with an acid chunk. */ | |||
| static const char* const acidOneShot; | |||
| /** Metadata property name used when reading a WAV file with an acid chunk. */ | |||
| static const char* const acidRootSet; | |||
| /** Metadata property name used when reading a WAV file with an acid chunk. */ | |||
| static const char* const acidStretch; | |||
| /** Metadata property name used when reading a WAV file with an acid chunk. */ | |||
| static const char* const acidDiskBased; | |||
| /** Metadata property name used when reading a WAV file with an acid chunk. */ | |||
| static const char* const acidizerFlag; | |||
| /** Metadata property name used when reading a WAV file with an acid chunk. */ | |||
| static const char* const acidRootNote; | |||
| /** Metadata property name used when reading a WAV file with an acid chunk. */ | |||
| static const char* const acidBeats; | |||
| /** Metadata property name used when reading a WAV file with an acid chunk. */ | |||
| static const char* const acidDenominator; | |||
| /** Metadata property name used when reading a WAV file with an acid chunk. */ | |||
| static const char* const acidNumerator; | |||
| /** Metadata property name used when reading a WAV file with an acid chunk. */ | |||
| static const char* const acidTempo; | |||
| // 'acid' chunk properties: | |||
| static const char* const acidOneShot; /**< Metadata property name used in acid chunks. */ | |||
| static const char* const acidRootSet; /**< Metadata property name used in acid chunks. */ | |||
| static const char* const acidStretch; /**< Metadata property name used in acid chunks. */ | |||
| static const char* const acidDiskBased; /**< Metadata property name used in acid chunks. */ | |||
| static const char* const acidizerFlag; /**< Metadata property name used in acid chunks. */ | |||
| static const char* const acidRootNote; /**< Metadata property name used in acid chunks. */ | |||
| static const char* const acidBeats; /**< Metadata property name used in acid chunks. */ | |||
| static const char* const acidDenominator; /**< Metadata property name used in acid chunks. */ | |||
| static const char* const acidNumerator; /**< Metadata property name used in acid chunks. */ | |||
| static const char* const acidTempo; /**< Metadata property name used in acid chunks. */ | |||
| //============================================================================== | |||
| // INFO chunk properties: | |||
| static const char* const riffInfoArchivalLocation; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoArtist; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoBaseURL; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoCinematographer; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoComment; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoComments; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoCommissioned; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoCopyright; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoCostumeDesigner; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoCountry; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoCropped; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoDateCreated; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoDateTimeOriginal; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoDefaultAudioStream; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoDimension; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoDirectory; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoDistributedBy; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoDotsPerInch; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoEditedBy; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoEighthLanguage; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoEncodedBy; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoEndTimecode; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoEngineer; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoFifthLanguage; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoFirstLanguage; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoFourthLanguage; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoGenre; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoKeywords; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoLanguage; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoLength; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoLightness; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoLocation; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoLogoIconURL; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoLogoURL; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoMedium; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoMoreInfoBannerImage; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoMoreInfoBannerURL; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoMoreInfoText; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoMoreInfoURL; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoMusicBy; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoNinthLanguage; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoNumberOfParts; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoOrganisation; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoPart; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoProducedBy; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoProductionDesigner; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoProductionStudio; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoRate; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoRated; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoRating; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoRippedBy; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoSecondaryGenre; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoSecondLanguage; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoSeventhLanguage; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoSharpness; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoSixthLanguage; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoSoftware; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoSoundSchemeTitle; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoSource; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoSourceFrom; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoStarring_ISTR; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoStarring_STAR; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoStartTimecode; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoStatistics; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoSubject; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoTapeName; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoTechnician; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoThirdLanguage; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoTimeCode; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoTitle; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoTrackNumber; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoURL; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoVegasVersionMajor; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoVegasVersionMinor; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoVersion; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoWatermarkURL; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoWrittenBy; /**< Metadata property name used in INFO chunks. */ | |||
| static const char* const riffInfoYear; /**< Metadata property name used in INFO chunks. */ | |||
| //============================================================================== | |||
| /** Metadata property name used when reading an ISRC code from an AXML chunk. */ | |||
| @@ -148,7 +183,7 @@ public: | |||
| AudioFormatReader* createReaderFor (InputStream* sourceStream, | |||
| bool deleteStreamIfOpeningFails) override; | |||
| MemoryMappedAudioFormatReader* createMemoryMappedReader (const File& file) override; | |||
| MemoryMappedAudioFormatReader* createMemoryMappedReader (const File&) override; | |||
| AudioFormatWriter* createWriterFor (OutputStream* streamToWriteTo, | |||
| double sampleRateToUse, | |||
| @@ -445,7 +445,7 @@ public: | |||
| /** Creates a new directory for this filename. | |||
| This will try to create the file as a directory, and fill also create | |||
| This will try to create the file as a directory, and will also create | |||
| any parent directories it needs in order to complete the operation. | |||
| @returns a result to indicate whether the directory was created successfully, or | |||
| @@ -208,7 +208,10 @@ public: | |||
| bool operator!= (Range other) const noexcept { return start != other.start || end != other.end; } | |||
| //============================================================================== | |||
| /** Returns true if the given position lies inside this range. */ | |||
| /** Returns true if the given position lies inside this range. | |||
| When making this comparison, the start value is considered to be inclusive, | |||
| and the end of the range exclusive. | |||
| */ | |||
| bool contains (const ValueType position) const noexcept | |||
| { | |||
| return start <= position && position < end; | |||
| @@ -220,10 +223,7 @@ public: | |||
| return jlimit (start, end, value); | |||
| } | |||
| /** 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. | |||
| */ | |||
| /** Returns true if the given range lies entirely inside this range. */ | |||
| bool contains (Range other) const noexcept | |||
| { | |||
| return start <= other.start && end >= other.end; | |||
| @@ -58,10 +58,10 @@ bool File::isHidden() const | |||
| return getFileName().startsWithChar ('.'); | |||
| } | |||
| static String getLinkedFile (StringRef file) | |||
| static String getLinkedFile (const String& file) | |||
| { | |||
| HeapBlock<char> buffer (8194); | |||
| const int numBytes = (int) readlink (file.text, buffer, 8192); | |||
| const int numBytes = (int) readlink (file.toRawUTF8(), buffer, 8192); | |||
| return String::fromUTF8 (buffer, jmax (0, numBytes)); | |||
| }; | |||
| @@ -209,7 +209,7 @@ namespace SocketHelpers | |||
| if (bytesThisTime <= 0 || ! connected) | |||
| { | |||
| if (bytesRead == 0) | |||
| if (bytesRead == 0 && blockUntilSpecifiedAmountHasArrived) | |||
| bytesRead = -1; | |||
| break; | |||
| @@ -648,22 +648,26 @@ int DatagramSocket::waitUntilReady (const bool readyForReading, | |||
| int DatagramSocket::read (void* destBuffer, int maxBytesToRead, bool shouldBlock) | |||
| { | |||
| if (handle < 0) | |||
| if (handle < 0 || ! isBound) | |||
| return -1; | |||
| bool connected = true; | |||
| return isBound ? SocketHelpers::readSocket (handle, destBuffer, maxBytesToRead, | |||
| connected, shouldBlock, readLock) : -1; | |||
| SocketHelpers::setSocketBlockingState (handle, shouldBlock); | |||
| return SocketHelpers::readSocket (handle, destBuffer, maxBytesToRead, | |||
| connected, shouldBlock, readLock); | |||
| } | |||
| int DatagramSocket::read (void* destBuffer, int maxBytesToRead, bool shouldBlock, String& senderIPAddress, int& senderPort) | |||
| { | |||
| if (handle < 0) | |||
| if (handle < 0 || ! isBound) | |||
| return -1; | |||
| bool connected = true; | |||
| return isBound ? SocketHelpers::readSocket (handle, destBuffer, maxBytesToRead, connected, | |||
| shouldBlock, readLock, &senderIPAddress, &senderPort) : -1; | |||
| SocketHelpers::setSocketBlockingState (handle, shouldBlock); | |||
| return SocketHelpers::readSocket (handle, destBuffer, maxBytesToRead, connected, | |||
| shouldBlock, readLock, &senderIPAddress, &senderPort); | |||
| } | |||
| int DatagramSocket::write (const String& remoteHostname, int remotePortNumber, | |||
| @@ -77,10 +77,12 @@ extern NewLine newLine; | |||
| myString << "Hello World" << newLine << newLine; | |||
| @endcode | |||
| */ | |||
| JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const NewLine&); | |||
| inline String& operator<< (String& string1, const NewLine&) { return string1 += NewLine::getDefault(); } | |||
| inline String& operator+= (String& s1, const NewLine&) { return s1 += NewLine::getDefault(); } | |||
| inline String operator+ (const NewLine&, const NewLine&) { return String (NewLine::getDefault()) + NewLine::getDefault(); } | |||
| inline String operator+ (String s1, const NewLine&) { return s1 += NewLine::getDefault(); } | |||
| inline String operator+ (const NewLine&, const char* s2) { return String (NewLine::getDefault()) + s2; } | |||
| #if JUCE_STRING_UTF_TYPE != 8 && ! defined (DOXYGEN) | |||
| inline String operator+ (String s1, const NewLine&) { return s1 += NewLine::getDefault(); } | |||
| #endif | |||
| #endif // JUCE_NEWLINE_H_INCLUDED | |||
| @@ -365,6 +365,7 @@ String::String (const CharPointer_UTF16 start, const CharPointer_UTF16 end) : t | |||
| String::String (const CharPointer_UTF32 start, const CharPointer_UTF32 end) : text (StringHolder::createFromCharPointer (start, end)) {} | |||
| String::String (const std::string& s) : text (StringHolder::createFromFixedLength (s.data(), s.size())) {} | |||
| String::String (StringRef s) : text (StringHolder::createFromCharPointer (s.text)) {} | |||
| String String::charToString (const juce_wchar character) | |||
| { | |||
| @@ -768,6 +769,11 @@ String& String::operator+= (const String& other) | |||
| return *this; | |||
| } | |||
| String& String::operator+= (StringRef other) | |||
| { | |||
| return operator+= (String (other)); | |||
| } | |||
| String& String::operator+= (const char ch) | |||
| { | |||
| const char asString[] = { ch, 0 }; | |||
| @@ -843,6 +849,7 @@ JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const wchar_t s2) | |||
| JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const char* const s2) { return s1 += s2; } | |||
| JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const wchar_t* const s2) { return s1 += s2; } | |||
| JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const String& s2) { return s1 += s2; } | |||
| JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, StringRef s2) { return s1 += s2; } | |||
| JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const int number) { return s1 += number; } | |||
| JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const short number) { return s1 += (int) number; } | |||
| @@ -874,11 +881,6 @@ JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, StringRef | |||
| return stream; | |||
| } | |||
| JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const NewLine&) | |||
| { | |||
| return string1 += NewLine::getDefault(); | |||
| } | |||
| //============================================================================== | |||
| int String::indexOfChar (const juce_wchar character) const noexcept | |||
| { | |||
| @@ -2308,6 +2310,10 @@ public: | |||
| expect (String ("abcdEFGH").toLowerCase() == String ("abcdefgh")); | |||
| expect (String ("abcdEFGH").toUpperCase() == String ("ABCDEFGH")); | |||
| expect (String (StringRef ("abc")) == "abc"); | |||
| expect (String (StringRef ("abc")) == StringRef ("abc")); | |||
| expect (String ("abc") + StringRef ("def") == "abcdef"); | |||
| String s2 ("123"); | |||
| s2 << ((int) 4) << ((short) 5) << "678" << L"9" << '0'; | |||
| s2 += "xyz"; | |||
| @@ -2316,6 +2322,8 @@ public: | |||
| expect (s2 == "1234567890xyz123"); | |||
| s2 += (int64) 123; | |||
| expect (s2 == "1234567890xyz123123"); | |||
| s2 << StringRef ("def"); | |||
| expect (s2 == "1234567890xyz123123def"); | |||
| beginTest ("Numeric conversions"); | |||
| expect (String::empty.getIntValue() == 0); | |||
| @@ -136,6 +136,9 @@ public: | |||
| /** Creates a string from a UTF-8 encoded std::string. */ | |||
| String (const std::string&); | |||
| /** Creates a string from a StringRef */ | |||
| String (StringRef); | |||
| //============================================================================== | |||
| /** Creates a string from a single character. */ | |||
| static String charToString (juce_wchar character); | |||
| @@ -202,6 +205,8 @@ public: | |||
| String& operator+= (const char* textToAppend); | |||
| /** Appends another string at the end of this one. */ | |||
| String& operator+= (const wchar_t* textToAppend); | |||
| /** Appends another string at the end of this one. */ | |||
| String& operator+= (StringRef textToAppend); | |||
| /** Appends a decimal number at the end of this string. */ | |||
| String& operator+= (int numberToAppend); | |||
| /** Appends a decimal number at the end of this string. */ | |||
| @@ -1289,6 +1294,8 @@ JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const char* string2) | |||
| JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const wchar_t* string2); | |||
| /** Appends a string to the end of the first one. */ | |||
| JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const String& string2); | |||
| /** Appends a string to the end of the first one. */ | |||
| JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, StringRef string2); | |||
| /** Appends a decimal number at the end of a string. */ | |||
| JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, short number); | |||
| @@ -131,8 +131,10 @@ JUCE_API bool JUCE_CALLTYPE operator== (const String& string1, StringRef string2 | |||
| /** Case-sensitive comparison of two strings. */ | |||
| JUCE_API bool JUCE_CALLTYPE operator!= (const String& string1, StringRef string2) noexcept; | |||
| #if JUCE_STRING_UTF_TYPE != 8 && ! defined (DOXYGEN) | |||
| inline String operator+ (String s1, StringRef s2) { return s1 += String (s2.text); } | |||
| #endif | |||
| inline String operator+ (String s1, StringRef s2) { return s1 += String (s2.text); } | |||
| inline String operator+ (StringRef s1, const String& s2) { return String (s1.text) + s2; } | |||
| inline String operator+ (const char* s1, StringRef s2) { return String (s1) + String (s2.text); } | |||
| inline String operator+ (StringRef s1, const char* s2) { return String (s1.text) + String (s2); } | |||
| #endif // JUCE_STRINGREF_H_INCLUDED | |||
| @@ -116,7 +116,21 @@ struct TextDiffHelpers | |||
| b, lenB, indexInB); | |||
| const size_t scratchSpace = sizeof (int) * (2 + 2 * (size_t) lenB); | |||
| int* const lines = (int*) alloca (scratchSpace); | |||
| if (scratchSpace < 4096) | |||
| { | |||
| int* scratch = (int*) alloca (scratchSpace); | |||
| return findLongestCommonSubstring (a, lenA, indexInA, b, lenB, indexInB, scratchSpace, scratch); | |||
| } | |||
| HeapBlock<int> scratch (scratchSpace); | |||
| return findLongestCommonSubstring (a, lenA, indexInA, b, lenB, indexInB, scratchSpace, scratch); | |||
| } | |||
| static int findLongestCommonSubstring (String::CharPointerType a, const int lenA, int& indexInA, | |||
| String::CharPointerType b, const int lenB, int& indexInB, | |||
| const size_t scratchSpace, int* const lines) noexcept | |||
| { | |||
| zeromem (lines, scratchSpace); | |||
| int* l0 = lines; | |||
| @@ -122,45 +122,50 @@ void TimeSliceThread::run() | |||
| { | |||
| Time nextClientTime; | |||
| int numClients = 0; | |||
| { | |||
| const ScopedLock sl2 (listLock); | |||
| index = clients.size() > 0 ? ((index + 1) % clients.size()) : 0; | |||
| numClients = clients.size(); | |||
| index = numClients > 0 ? ((index + 1) % numClients) : 0; | |||
| if (TimeSliceClient* const firstClient = getNextClient (index)) | |||
| nextClientTime = firstClient->nextCallTime; | |||
| } | |||
| const Time now (Time::getCurrentTime()); | |||
| if (nextClientTime > now) | |||
| { | |||
| timeToWait = (int) jmin ((int64) 500, (nextClientTime - now).inMilliseconds()); | |||
| } | |||
| else | |||
| if (numClients > 0) | |||
| { | |||
| timeToWait = index == 0 ? 1 : 0; | |||
| const ScopedLock sl (callbackLock); | |||
| const Time now (Time::getCurrentTime()); | |||
| if (nextClientTime > now) | |||
| { | |||
| const ScopedLock sl2 (listLock); | |||
| clientBeingCalled = getNextClient (index); | |||
| timeToWait = (int) jmin ((int64) 500, (nextClientTime - now).inMilliseconds()); | |||
| } | |||
| if (clientBeingCalled != nullptr) | |||
| else | |||
| { | |||
| const int msUntilNextCall = clientBeingCalled->useTimeSlice(); | |||
| timeToWait = index == 0 ? 1 : 0; | |||
| const ScopedLock sl (callbackLock); | |||
| { | |||
| const ScopedLock sl2 (listLock); | |||
| clientBeingCalled = getNextClient (index); | |||
| } | |||
| if (clientBeingCalled != nullptr) | |||
| { | |||
| const int msUntilNextCall = clientBeingCalled->useTimeSlice(); | |||
| const ScopedLock sl2 (listLock); | |||
| const ScopedLock sl2 (listLock); | |||
| if (msUntilNextCall >= 0) | |||
| clientBeingCalled->nextCallTime = now + RelativeTime::milliseconds (msUntilNextCall); | |||
| else | |||
| clients.removeFirstMatchingValue (clientBeingCalled); | |||
| if (msUntilNextCall >= 0) | |||
| clientBeingCalled->nextCallTime = now + RelativeTime::milliseconds (msUntilNextCall); | |||
| else | |||
| clients.removeFirstMatchingValue (clientBeingCalled); | |||
| clientBeingCalled = nullptr; | |||
| clientBeingCalled = nullptr; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -26,6 +26,40 @@ | |||
| ============================================================================== | |||
| */ | |||
| namespace | |||
| { | |||
| inline bool isValidXmlNameStartCharacter (const juce_wchar character) noexcept | |||
| { | |||
| return character == ':' | |||
| || character == '_' | |||
| || (character >= 'a' && character <= 'z') | |||
| || (character >= 'A' && character <= 'Z') | |||
| || (character >= 0xc0 && character <= 0xd6) | |||
| || (character >= 0xd8 && character <= 0xf6) | |||
| || (character >= 0xf8 && character <= 0x2ff) | |||
| || (character >= 0x370 && character <= 0x37d) | |||
| || (character >= 0x37f && character <= 0x1fff) | |||
| || (character >= 0x200c && character <= 0x200d) | |||
| || (character >= 0x2070 && character <= 0x218f) | |||
| || (character >= 0x2c00 && character <= 0x2fef) | |||
| || (character >= 0x3001 && character <= 0xd7ff) | |||
| || (character >= 0xf900 && character <= 0xfdcf) | |||
| || (character >= 0xfdf0 && character <= 0xfffd) | |||
| || (character >= 0x10000 && character <= 0xeffff); | |||
| } | |||
| inline bool isValidXmlNameBodyCharacter (const juce_wchar character) noexcept | |||
| { | |||
| return isValidXmlNameStartCharacter (character) | |||
| || character == '-' | |||
| || character == '.' | |||
| || character == 0xb7 | |||
| || (character >= '0' && character <= '9') | |||
| || (character >= 0x300 && character <= 0x036f) | |||
| || (character >= 0x203f && character <= 0x2040); | |||
| } | |||
| } | |||
| XmlElement::XmlAttributeNode::XmlAttributeNode (const XmlAttributeNode& other) noexcept | |||
| : name (other.name), | |||
| value (other.value) | |||
| @@ -35,58 +69,44 @@ XmlElement::XmlAttributeNode::XmlAttributeNode (const XmlAttributeNode& other) n | |||
| XmlElement::XmlAttributeNode::XmlAttributeNode (const Identifier& n, const String& v) noexcept | |||
| : name (n), value (v) | |||
| { | |||
| #if JUCE_DEBUG | |||
| // this checks whether the attribute name string contains any illegal characters.. | |||
| for (String::CharPointerType t (name.getCharPointer()); ! t.isEmpty(); ++t) | |||
| jassert (t.isLetterOrDigit() || *t == '_' || *t == '-' || *t == ':'); | |||
| #endif | |||
| jassert (isValidXmlName (name)); | |||
| } | |||
| XmlElement::XmlAttributeNode::XmlAttributeNode (String::CharPointerType nameStart, String::CharPointerType nameEnd) | |||
| : name (nameStart, nameEnd) | |||
| { | |||
| jassert (isValidXmlName (name)); | |||
| } | |||
| //============================================================================== | |||
| static void sanityCheckTagName (const String& tag) | |||
| { | |||
| (void) tag; | |||
| // the tag name mustn't be empty, or it'll look like a text element! | |||
| jassert (tag.containsNonWhitespaceChars()); | |||
| // The tag can't contain spaces or other characters that would create invalid XML! | |||
| jassert (! tag.containsAnyOf (" <>/&(){}")); | |||
| } | |||
| XmlElement::XmlElement (const String& tag) | |||
| : tagName (StringPool::getGlobalPool().getPooledString (tag)) | |||
| { | |||
| sanityCheckTagName (tagName); | |||
| jassert (isValidXmlName (tagName)); | |||
| } | |||
| XmlElement::XmlElement (const char* tag) | |||
| : tagName (StringPool::getGlobalPool().getPooledString (tag)) | |||
| { | |||
| sanityCheckTagName (tagName); | |||
| jassert (isValidXmlName (tagName)); | |||
| } | |||
| XmlElement::XmlElement (StringRef tag) | |||
| : tagName (StringPool::getGlobalPool().getPooledString (tag)) | |||
| { | |||
| sanityCheckTagName (tagName); | |||
| jassert (isValidXmlName (tagName)); | |||
| } | |||
| XmlElement::XmlElement (const Identifier& tag) | |||
| : tagName (tag.toString()) | |||
| { | |||
| sanityCheckTagName (tagName); | |||
| jassert (isValidXmlName (tagName)); | |||
| } | |||
| XmlElement::XmlElement (String::CharPointerType tagNameStart, String::CharPointerType tagNameEnd) | |||
| : tagName (StringPool::getGlobalPool().getPooledString (tagNameStart, tagNameEnd)) | |||
| { | |||
| sanityCheckTagName (tagName); | |||
| jassert (isValidXmlName (tagName)); | |||
| } | |||
| XmlElement::XmlElement (int /*dummy*/) noexcept | |||
| @@ -869,6 +889,21 @@ XmlElement* XmlElement::createTextElement (const String& text) | |||
| return e; | |||
| } | |||
| bool XmlElement::isValidXmlName (StringRef text) noexcept | |||
| { | |||
| if (text.isEmpty() || ! isValidXmlNameStartCharacter (text.text.getAndAdvance())) | |||
| return false; | |||
| for (;;) | |||
| { | |||
| if (text.isEmpty()) | |||
| return true; | |||
| if (! isValidXmlNameBodyCharacter (text.text.getAndAdvance())) | |||
| return false; | |||
| } | |||
| } | |||
| void XmlElement::addTextElement (const String& text) | |||
| { | |||
| addChildElement (createTextElement (text)); | |||
| @@ -724,6 +724,9 @@ public: | |||
| /** Creates a text element that can be added to a parent element. */ | |||
| static XmlElement* createTextElement (const String& text); | |||
| /** Checks if a given string is a valid XML name */ | |||
| static bool isValidXmlName (StringRef possibleName) noexcept; | |||
| //============================================================================== | |||
| private: | |||
| struct XmlAttributeNode | |||
| @@ -1066,7 +1066,12 @@ public: | |||
| for (int i = 1 + r.nextInt (numElementsInArray (buffer) - 2); --i >= 0;) | |||
| buffer[i] = chars [r.nextInt (sizeof (chars) - 1)]; | |||
| return CharPointer_ASCII (buffer); | |||
| String result (buffer); | |||
| if (! XmlElement::isValidXmlName (result)) | |||
| result = createRandomIdentifier (r); | |||
| return result; | |||
| } | |||
| static String createRandomWideCharString (Random& r) | |||
| @@ -226,14 +226,14 @@ public: | |||
| */ | |||
| ValueTree getChild (int index) const; | |||
| /** Returns the first child node with the speficied type name. | |||
| /** Returns the first child node with the specified type name. | |||
| If no such node is found, it'll return an invalid node. (See isValid() to find out | |||
| whether a node is valid). | |||
| @see getOrCreateChildWithName | |||
| */ | |||
| ValueTree getChildWithName (const Identifier& type) const; | |||
| /** Returns the first child node with the speficied type name, creating and adding | |||
| /** Returns the first child node with the specified type name, creating and adding | |||
| a child with this name if there wasn't already one there. | |||
| The only time this will return an invalid object is when the object that you're calling | |||
| @@ -242,7 +242,7 @@ public: | |||
| */ | |||
| ValueTree getOrCreateChildWithName (const Identifier& type, UndoManager* undoManager); | |||
| /** Looks for the first child node that has the speficied property value. | |||
| /** Looks for the first child node that has the specified property value. | |||
| This will scan the child nodes in order, until it finds one that has property that matches | |||
| the specified value. | |||
| @@ -31,14 +31,15 @@ public: | |||
| TimerThread() | |||
| : Thread ("Juce Timer"), | |||
| firstTimer (nullptr), | |||
| callbackNeeded (0) | |||
| firstTimer (nullptr) | |||
| { | |||
| triggerAsyncUpdate(); | |||
| } | |||
| ~TimerThread() noexcept | |||
| { | |||
| signalThreadShouldExit(); | |||
| callbackArrived.signal(); | |||
| stopThread (4000); | |||
| jassert (instance == this || instance == nullptr); | |||
| @@ -55,12 +56,6 @@ public: | |||
| { | |||
| const uint32 now = Time::getMillisecondCounter(); | |||
| if (now == lastTime) | |||
| { | |||
| wait (1); | |||
| continue; | |||
| } | |||
| const int elapsed = (int) (now >= lastTime ? (now - lastTime) | |||
| : (std::numeric_limits<uint32>::max() - (lastTime - now))); | |||
| lastTime = now; | |||
| @@ -69,42 +64,29 @@ public: | |||
| if (timeUntilFirstTimer <= 0) | |||
| { | |||
| /* If we managed to set the atomic boolean to true then send a message, this is needed | |||
| as a memory barrier so the message won't be sent before callbackNeeded is set to true, | |||
| but if it fails it means the message-thread changed the value from under us so at least | |||
| some processing is happenening and we can just loop around and try again | |||
| */ | |||
| if (callbackNeeded.compareAndSetBool (1, 0)) | |||
| if (callbackArrived.wait (0)) | |||
| { | |||
| // already a message in flight - do nothing.. | |||
| } | |||
| else | |||
| { | |||
| messageToSend->post(); | |||
| /* Sometimes our message can get discarded by the OS (e.g. when running as an RTAS | |||
| when the app has a modal loop), so this is how long to wait before assuming the | |||
| message has been lost and trying again. | |||
| */ | |||
| const uint32 messageDeliveryTimeout = now + 300; | |||
| while (callbackNeeded.get() != 0) | |||
| if (! callbackArrived.wait (300)) | |||
| { | |||
| wait (4); | |||
| if (threadShouldExit()) | |||
| return; | |||
| if (Time::getMillisecondCounter() > messageDeliveryTimeout) | |||
| { | |||
| messageToSend->post(); | |||
| break; | |||
| } | |||
| // Sometimes our message can get discarded by the OS (e.g. when running as an RTAS | |||
| // when the app has a modal loop), so this is how long to wait before assuming the | |||
| // message has been lost and trying again. | |||
| messageToSend->post(); | |||
| } | |||
| continue; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| // don't wait for too long because running this loop also helps keep the | |||
| // Time::getApproximateMillisecondTimer value stay up-to-date | |||
| wait (jlimit (1, 50, timeUntilFirstTimer)); | |||
| } | |||
| // don't wait for too long because running this loop also helps keep the | |||
| // Time::getApproximateMillisecondTimer value stay up-to-date | |||
| wait (jlimit (1, 100, timeUntilFirstTimer)); | |||
| } | |||
| } | |||
| @@ -129,13 +111,7 @@ public: | |||
| JUCE_CATCH_EXCEPTION | |||
| } | |||
| /* This is needed as a memory barrier to make sure all processing of current timers is done | |||
| before the boolean is set. This set should never fail since if it was false in the first place, | |||
| we wouldn't get a message (so it can't be changed from false to true from under us), and if we | |||
| get a message then the value is true and the other thread can only set it to true again and | |||
| we will get another callback to set it to false. | |||
| */ | |||
| callbackNeeded.set (0); | |||
| callbackArrived.signal(); | |||
| } | |||
| void callTimersSynchronously() | |||
| @@ -186,7 +162,7 @@ public: | |||
| private: | |||
| Timer* volatile firstTimer; | |||
| Atomic<int> callbackNeeded; | |||
| WaitableEvent callbackArrived; | |||
| struct CallTimersMessage : public MessageManager::MessageBase | |||
| { | |||
| @@ -282,30 +282,25 @@ public: | |||
| Colour withBrightness (float newBrightness) const noexcept; | |||
| /** Returns a copy of this colour with it hue rotated. | |||
| The new colour's hue is ((this->getHue() + amountToRotate) % 1.0) | |||
| @see brighter, darker, withMultipliedBrightness | |||
| */ | |||
| Colour withRotatedHue (float amountToRotate) const noexcept; | |||
| /** Returns a copy of this colour with its saturation multiplied by the given value. | |||
| The new colour's saturation is (this->getSaturation() * multiplier) | |||
| (the result is clipped to legal limits). | |||
| */ | |||
| Colour withMultipliedSaturation (float multiplier) const noexcept; | |||
| /** Returns a copy of this colour with its brightness multiplied by the given value. | |||
| The new colour's saturation is (this->getBrightness() * multiplier) | |||
| The new colour's brightness is (this->getBrightness() * multiplier) | |||
| (the result is clipped to legal limits). | |||
| */ | |||
| Colour withMultipliedBrightness (float amount) const noexcept; | |||
| //============================================================================== | |||
| /** Returns a brighter version of this colour. | |||
| @param amountBrighter how much brighter to make it - a value from 0 to 1.0 where 0 is | |||
| unchanged, and higher values make it brighter | |||
| @see withMultipliedBrightness | |||
| @@ -313,7 +308,6 @@ public: | |||
| Colour brighter (float amountBrighter = 0.4f) const noexcept; | |||
| /** Returns a darker version of this colour. | |||
| @param amountDarker how much darker to make it - a value from 0 to 1.0 where 0 is | |||
| unchanged, and higher values make it darker | |||
| @see withMultipliedBrightness | |||
| @@ -197,11 +197,10 @@ ComboBox* AlertWindow::getComboBoxComponent (const String& nameOfList) const | |||
| } | |||
| //============================================================================== | |||
| class AlertTextComp : public TextEditor | |||
| class AlertTextComp : public TextEditor | |||
| { | |||
| public: | |||
| AlertTextComp (const String& message, | |||
| const Font& font) | |||
| AlertTextComp (AlertWindow& owner, const String& message, const Font& font) | |||
| { | |||
| setReadOnly (true); | |||
| setMultiLine (true, true); | |||
| @@ -215,6 +214,9 @@ public: | |||
| bestWidth = 2 * (int) std::sqrt (font.getHeight() * font.getStringWidth (message)); | |||
| if (owner.isColourSpecified (AlertWindow::textColourId)) | |||
| setColour (TextEditor::textColourId, owner.findColour (AlertWindow::textColourId)); | |||
| setColour (TextEditor::backgroundColourId, Colours::transparentBlack); | |||
| setColour (TextEditor::outlineColourId, Colours::transparentBlack); | |||
| setColour (TextEditor::shadowColourId, Colours::transparentBlack); | |||
| @@ -241,7 +243,7 @@ private: | |||
| void AlertWindow::addTextBlock (const String& textBlock) | |||
| { | |||
| AlertTextComp* const c = new AlertTextComp (textBlock, getLookAndFeel().getAlertWindowMessageFont()); | |||
| AlertTextComp* const c = new AlertTextComp (*this, textBlock, getLookAndFeel().getAlertWindowMessageFont()); | |||
| textBlocks.add (c); | |||
| allComps.add (c); | |||
| @@ -428,18 +430,9 @@ void AlertWindow::updateLayout (const bool onlyIncreaseSize) | |||
| } | |||
| if (! isVisible()) | |||
| { | |||
| centreAroundComponent (associatedComponent, w, h); | |||
| } | |||
| else | |||
| { | |||
| const int cx = getX() + getWidth() / 2; | |||
| const int cy = getY() + getHeight() / 2; | |||
| setBounds (cx - w / 2, | |||
| cy - h / 2, | |||
| w, h); | |||
| } | |||
| setBounds (getBounds().withSizeKeepingCentre (w, h)); | |||
| textArea.setBounds (edgeGap, edgeGap, w - (edgeGap * 2), h - edgeGap); | |||
| @@ -99,9 +99,10 @@ public: | |||
| // Check for the plugin creating its own floating window, and if there is one, | |||
| // we need to reparent it to make it visible.. | |||
| if (NSWindow* floatingChildWindow = [[carbonWindow childWindows] objectAtIndex: 0]) | |||
| [getOwnerWindow() addChildWindow: floatingChildWindow | |||
| ordered: NSWindowAbove]; | |||
| if (carbonWindow.childWindows.count > 0) | |||
| if (NSWindow* floatingChildWindow = [[carbonWindow childWindows] objectAtIndex: 0]) | |||
| [getOwnerWindow() addChildWindow: floatingChildWindow | |||
| ordered: NSWindowAbove]; | |||
| EventTypeSpec windowEventTypes[] = | |||
| { | |||