This reverts commit 3870412e57.
tags/v1.9.11
| @@ -0,0 +1,976 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the Water library. | |||||
| Copyright (c) 2015 ROLI Ltd. | |||||
| Copyright (C) 2018 Filipe Coelho <falktx@falktx.com> | |||||
| Permission is granted to use this software under the terms of either: | |||||
| a) the GPL v2 (or any later version) | |||||
| b) the Affero GPL v3 | |||||
| Details of these licenses can be found at: www.gnu.org/licenses | |||||
| Water is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
| ============================================================================== | |||||
| */ | |||||
| #include "WavAudioFormat.h" | |||||
| #include "../audioformat/AudioFormatReader.h" | |||||
| #include "../memory/ByteOrder.h" | |||||
| #include "../memory/MemoryBlock.h" | |||||
| #include "../streams/InputStream.h" | |||||
| #include "../streams/MemoryOutputStream.h" | |||||
| #include "../xml/XmlDocument.h" | |||||
| #include "../xml/XmlElement.h" | |||||
| namespace water { | |||||
| static const char* const wavFormatName = "WAV file"; | |||||
| //============================================================================== | |||||
| const char* const WavAudioFormat::bwavDescription = "bwav description"; | |||||
| const char* const WavAudioFormat::bwavOriginator = "bwav originator"; | |||||
| const char* const WavAudioFormat::bwavOriginatorRef = "bwav originator ref"; | |||||
| const char* const WavAudioFormat::bwavOriginationDate = "bwav origination date"; | |||||
| const char* const WavAudioFormat::bwavOriginationTime = "bwav origination time"; | |||||
| const char* const WavAudioFormat::bwavTimeReference = "bwav time reference"; | |||||
| const char* const WavAudioFormat::bwavCodingHistory = "bwav coding history"; | |||||
| #if 0 | |||||
| StringPairArray WavAudioFormat::createBWAVMetadata (const String& description, | |||||
| const String& originator, | |||||
| const String& originatorRef, | |||||
| Time date, | |||||
| const int64 timeReferenceSamples, | |||||
| const String& codingHistory) | |||||
| { | |||||
| StringPairArray m; | |||||
| m.set (bwavDescription, description); | |||||
| m.set (bwavOriginator, originator); | |||||
| m.set (bwavOriginatorRef, originatorRef); | |||||
| m.set (bwavOriginationDate, date.formatted ("%Y-%m-%d")); | |||||
| m.set (bwavOriginationTime, date.formatted ("%H:%M:%S")); | |||||
| m.set (bwavTimeReference, String (timeReferenceSamples)); | |||||
| m.set (bwavCodingHistory, codingHistory); | |||||
| return m; | |||||
| } | |||||
| #endif | |||||
| const char* const WavAudioFormat::acidOneShot = "acid one shot"; | |||||
| const char* const WavAudioFormat::acidRootSet = "acid root set"; | |||||
| const char* const WavAudioFormat::acidStretch = "acid stretch"; | |||||
| const char* const WavAudioFormat::acidDiskBased = "acid disk based"; | |||||
| const char* const WavAudioFormat::acidizerFlag = "acidizer flag"; | |||||
| const char* const WavAudioFormat::acidRootNote = "acid root note"; | |||||
| const char* const WavAudioFormat::acidBeats = "acid beats"; | |||||
| 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"; | |||||
| //============================================================================== | |||||
| namespace WavFileHelpers | |||||
| { | |||||
| inline int chunkName (const char* const name) noexcept { return (int) ByteOrder::littleEndianInt (name); } | |||||
| inline size_t roundUpSize (size_t sz) noexcept { return (sz + 3) & ~3u; } | |||||
| #if _MSVC_VER | |||||
| #pragma pack (push, 1) | |||||
| #endif | |||||
| struct BWAVChunk | |||||
| { | |||||
| char description [256]; | |||||
| char originator [32]; | |||||
| char originatorRef [32]; | |||||
| char originationDate [10]; | |||||
| char originationTime [8]; | |||||
| uint32 timeRefLow; | |||||
| uint32 timeRefHigh; | |||||
| uint16 version; | |||||
| uint8 umid[64]; | |||||
| uint8 reserved[190]; | |||||
| char codingHistory[1]; | |||||
| void copyTo (StringPairArray& values, const int totalSize) const | |||||
| { | |||||
| values.set (WavAudioFormat::bwavDescription, String::fromUTF8 (description, sizeof (description))); | |||||
| values.set (WavAudioFormat::bwavOriginator, String::fromUTF8 (originator, sizeof (originator))); | |||||
| values.set (WavAudioFormat::bwavOriginatorRef, String::fromUTF8 (originatorRef, sizeof (originatorRef))); | |||||
| values.set (WavAudioFormat::bwavOriginationDate, String::fromUTF8 (originationDate, sizeof (originationDate))); | |||||
| values.set (WavAudioFormat::bwavOriginationTime, String::fromUTF8 (originationTime, sizeof (originationTime))); | |||||
| const uint32 timeLow = ByteOrder::swapIfBigEndian (timeRefLow); | |||||
| const uint32 timeHigh = ByteOrder::swapIfBigEndian (timeRefHigh); | |||||
| const int64 time = (((int64) timeHigh) << 32) + timeLow; | |||||
| values.set (WavAudioFormat::bwavTimeReference, String (time)); | |||||
| values.set (WavAudioFormat::bwavCodingHistory, | |||||
| String::fromUTF8 (codingHistory, totalSize - (int) offsetof (BWAVChunk, codingHistory))); | |||||
| } | |||||
| } WATER_PACKED; | |||||
| //============================================================================== | |||||
| struct SMPLChunk | |||||
| { | |||||
| struct SampleLoop | |||||
| { | |||||
| uint32 identifier; | |||||
| uint32 type; // these are different in AIFF and WAV | |||||
| uint32 start; | |||||
| uint32 end; | |||||
| uint32 fraction; | |||||
| uint32 playCount; | |||||
| } WATER_PACKED; | |||||
| uint32 manufacturer; | |||||
| uint32 product; | |||||
| uint32 samplePeriod; | |||||
| uint32 midiUnityNote; | |||||
| uint32 midiPitchFraction; | |||||
| uint32 smpteFormat; | |||||
| uint32 smpteOffset; | |||||
| uint32 numSampleLoops; | |||||
| uint32 samplerData; | |||||
| SampleLoop loops[1]; | |||||
| template <typename NameType> | |||||
| static void setValue (StringPairArray& values, NameType name, uint32 val) | |||||
| { | |||||
| values.set (name, String (ByteOrder::swapIfBigEndian (val))); | |||||
| } | |||||
| static void setValue (StringPairArray& values, int prefix, const char* name, uint32 val) | |||||
| { | |||||
| setValue (values, "Loop" + String (prefix) + name, val); | |||||
| } | |||||
| void copyTo (StringPairArray& values, const int totalSize) const | |||||
| { | |||||
| setValue (values, "Manufacturer", manufacturer); | |||||
| setValue (values, "Product", product); | |||||
| setValue (values, "SamplePeriod", samplePeriod); | |||||
| setValue (values, "MidiUnityNote", midiUnityNote); | |||||
| setValue (values, "MidiPitchFraction", midiPitchFraction); | |||||
| setValue (values, "SmpteFormat", smpteFormat); | |||||
| setValue (values, "SmpteOffset", smpteOffset); | |||||
| setValue (values, "NumSampleLoops", numSampleLoops); | |||||
| setValue (values, "SamplerData", samplerData); | |||||
| for (int i = 0; i < (int) numSampleLoops; ++i) | |||||
| { | |||||
| if ((uint8*) (loops + (i + 1)) > ((uint8*) this) + totalSize) | |||||
| break; | |||||
| setValue (values, i, "Identifier", loops[i].identifier); | |||||
| setValue (values, i, "Type", loops[i].type); | |||||
| setValue (values, i, "Start", loops[i].start); | |||||
| setValue (values, i, "End", loops[i].end); | |||||
| setValue (values, i, "Fraction", loops[i].fraction); | |||||
| setValue (values, i, "PlayCount", loops[i].playCount); | |||||
| } | |||||
| } | |||||
| template <typename NameType> | |||||
| static uint32 getValue (const StringPairArray& values, NameType name, const char* def) | |||||
| { | |||||
| return ByteOrder::swapIfBigEndian ((uint32) values.getValue (name, def).getIntValue()); | |||||
| } | |||||
| static uint32 getValue (const StringPairArray& values, int prefix, const char* name, const char* def) | |||||
| { | |||||
| return getValue (values, "Loop" + String (prefix) + name, def); | |||||
| } | |||||
| } WATER_PACKED; | |||||
| //============================================================================== | |||||
| struct InstChunk | |||||
| { | |||||
| int8 baseNote; | |||||
| int8 detune; | |||||
| int8 gain; | |||||
| int8 lowNote; | |||||
| int8 highNote; | |||||
| int8 lowVelocity; | |||||
| int8 highVelocity; | |||||
| static void setValue (StringPairArray& values, const char* name, int val) | |||||
| { | |||||
| values.set (name, String (val)); | |||||
| } | |||||
| void copyTo (StringPairArray& values) const | |||||
| { | |||||
| setValue (values, "MidiUnityNote", baseNote); | |||||
| setValue (values, "Detune", detune); | |||||
| setValue (values, "Gain", gain); | |||||
| setValue (values, "LowNote", lowNote); | |||||
| setValue (values, "HighNote", highNote); | |||||
| setValue (values, "LowVelocity", lowVelocity); | |||||
| setValue (values, "HighVelocity", highVelocity); | |||||
| } | |||||
| static int8 getValue (const StringPairArray& values, const char* name, const char* def) | |||||
| { | |||||
| return (int8) values.getValue (name, def).getIntValue(); | |||||
| } | |||||
| } WATER_PACKED; | |||||
| //============================================================================== | |||||
| struct CueChunk | |||||
| { | |||||
| struct Cue | |||||
| { | |||||
| uint32 identifier; | |||||
| uint32 order; | |||||
| uint32 chunkID; | |||||
| uint32 chunkStart; | |||||
| uint32 blockStart; | |||||
| uint32 offset; | |||||
| } WATER_PACKED; | |||||
| uint32 numCues; | |||||
| Cue cues[1]; | |||||
| static void setValue (StringPairArray& values, int prefix, const char* name, uint32 val) | |||||
| { | |||||
| values.set ("Cue" + String (prefix) + name, String (ByteOrder::swapIfBigEndian (val))); | |||||
| } | |||||
| void copyTo (StringPairArray& values, const int totalSize) const | |||||
| { | |||||
| values.set ("NumCuePoints", String (ByteOrder::swapIfBigEndian (numCues))); | |||||
| for (int i = 0; i < (int) numCues; ++i) | |||||
| { | |||||
| if ((uint8*) (cues + (i + 1)) > ((uint8*) this) + totalSize) | |||||
| break; | |||||
| setValue (values, i, "Identifier", cues[i].identifier); | |||||
| setValue (values, i, "Order", cues[i].order); | |||||
| setValue (values, i, "ChunkID", cues[i].chunkID); | |||||
| setValue (values, i, "ChunkStart", cues[i].chunkStart); | |||||
| setValue (values, i, "BlockStart", cues[i].blockStart); | |||||
| setValue (values, i, "Offset", cues[i].offset); | |||||
| } | |||||
| } | |||||
| } WATER_PACKED; | |||||
| //============================================================================== | |||||
| /** 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 ((uint32) name[i] != CharacterFunctions::toUpperCase ((uint32) ((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(); | |||||
| int64 infoLength = chunkEnd - input.getPosition(); | |||||
| if (infoLength > 0) | |||||
| { | |||||
| infoLength = jmin (infoLength, (int64) input.readInt()); | |||||
| if (infoLength <= 0) | |||||
| return; | |||||
| 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], String::createStringFromData ((const char*) mb.getData(), | |||||
| (int) mb.getSize())); | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| struct AcidChunk | |||||
| { | |||||
| /** Reads an acid RIFF chunk from a stream positioned just after the size byte. */ | |||||
| AcidChunk (InputStream& input, size_t length) | |||||
| { | |||||
| zerostruct (*this); | |||||
| input.read (this, (int) jmin (sizeof (*this), length)); | |||||
| } | |||||
| AcidChunk (const StringPairArray& values) | |||||
| { | |||||
| zerostruct (*this); | |||||
| flags = getFlagIfPresent (values, WavAudioFormat::acidOneShot, 0x01) | |||||
| | getFlagIfPresent (values, WavAudioFormat::acidRootSet, 0x02) | |||||
| | getFlagIfPresent (values, WavAudioFormat::acidStretch, 0x04) | |||||
| | getFlagIfPresent (values, WavAudioFormat::acidDiskBased, 0x08) | |||||
| | getFlagIfPresent (values, WavAudioFormat::acidizerFlag, 0x10); | |||||
| if (values[WavAudioFormat::acidRootSet].getIntValue() != 0) | |||||
| rootNote = ByteOrder::swapIfBigEndian ((uint16) values[WavAudioFormat::acidRootNote].getIntValue()); | |||||
| numBeats = ByteOrder::swapIfBigEndian ((uint32) values[WavAudioFormat::acidBeats].getIntValue()); | |||||
| meterDenominator = ByteOrder::swapIfBigEndian ((uint16) values[WavAudioFormat::acidDenominator].getIntValue()); | |||||
| meterNumerator = ByteOrder::swapIfBigEndian ((uint16) values[WavAudioFormat::acidNumerator].getIntValue()); | |||||
| if (values.containsKey (WavAudioFormat::acidTempo)) | |||||
| tempo = swapFloatByteOrder (values[WavAudioFormat::acidTempo].getFloatValue()); | |||||
| } | |||||
| MemoryBlock toMemoryBlock() const | |||||
| { | |||||
| return (flags != 0 || rootNote != 0 || numBeats != 0 || meterDenominator != 0 || meterNumerator != 0) | |||||
| ? MemoryBlock (this, sizeof (*this)) : MemoryBlock(); | |||||
| } | |||||
| void addToMetadata (StringPairArray& values) const | |||||
| { | |||||
| setBoolFlag (values, WavAudioFormat::acidOneShot, 0x01); | |||||
| setBoolFlag (values, WavAudioFormat::acidRootSet, 0x02); | |||||
| setBoolFlag (values, WavAudioFormat::acidStretch, 0x04); | |||||
| setBoolFlag (values, WavAudioFormat::acidDiskBased, 0x08); | |||||
| setBoolFlag (values, WavAudioFormat::acidizerFlag, 0x10); | |||||
| if (flags & 0x02) // root note set | |||||
| values.set (WavAudioFormat::acidRootNote, String (ByteOrder::swapIfBigEndian (rootNote))); | |||||
| values.set (WavAudioFormat::acidBeats, String (ByteOrder::swapIfBigEndian (numBeats))); | |||||
| values.set (WavAudioFormat::acidDenominator, String (ByteOrder::swapIfBigEndian (meterDenominator))); | |||||
| values.set (WavAudioFormat::acidNumerator, String (ByteOrder::swapIfBigEndian (meterNumerator))); | |||||
| values.set (WavAudioFormat::acidTempo, String (swapFloatByteOrder (tempo))); | |||||
| } | |||||
| void setBoolFlag (StringPairArray& values, const char* name, uint32 mask) const | |||||
| { | |||||
| values.set (name, (flags & ByteOrder::swapIfBigEndian (mask)) ? "1" : "0"); | |||||
| } | |||||
| static uint32 getFlagIfPresent (const StringPairArray& values, const char* name, uint32 flag) | |||||
| { | |||||
| return values[name].getIntValue() != 0 ? ByteOrder::swapIfBigEndian (flag) : 0; | |||||
| } | |||||
| static float swapFloatByteOrder (const float x) noexcept | |||||
| { | |||||
| #if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ | |||||
| union { uint32 asInt; float asFloat; } n; | |||||
| n.asFloat = x; | |||||
| n.asInt = ByteOrder::swap (n.asInt); | |||||
| return n.asFloat; | |||||
| #else | |||||
| return x; | |||||
| #endif | |||||
| } | |||||
| uint32 flags; | |||||
| uint16 rootNote; | |||||
| uint16 reserved1; | |||||
| float reserved2; | |||||
| uint32 numBeats; | |||||
| uint16 meterDenominator; | |||||
| uint16 meterNumerator; | |||||
| float tempo; | |||||
| } WATER_PACKED; | |||||
| //============================================================================== | |||||
| namespace AXMLChunk | |||||
| { | |||||
| static void addToMetadata (StringPairArray& destValues, const String& source) | |||||
| { | |||||
| ScopedPointer<XmlElement> xml (XmlDocument::parse (source)); | |||||
| if (xml != nullptr && xml->hasTagName ("ebucore:ebuCoreMain")) | |||||
| { | |||||
| if (XmlElement* xml2 = xml->getChildByName ("ebucore:coreMetadata")) | |||||
| { | |||||
| if (XmlElement* xml3 = xml2->getChildByName ("ebucore:identifier")) | |||||
| { | |||||
| if (XmlElement* xml4 = xml3->getChildByName ("dc:identifier")) | |||||
| { | |||||
| const String ISRCCode (xml4->getAllSubText().fromFirstOccurrenceOf ("ISRC:", false, true)); | |||||
| if (ISRCCode.isNotEmpty()) | |||||
| destValues.set (WavAudioFormat::ISRC, ISRCCode); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| }; | |||||
| //============================================================================== | |||||
| struct ExtensibleWavSubFormat | |||||
| { | |||||
| uint32 data1; | |||||
| uint16 data2; | |||||
| uint16 data3; | |||||
| uint8 data4[8]; | |||||
| bool operator== (const ExtensibleWavSubFormat& other) const noexcept { return memcmp (this, &other, sizeof (*this)) == 0; } | |||||
| bool operator!= (const ExtensibleWavSubFormat& other) const noexcept { return ! operator== (other); } | |||||
| } WATER_PACKED; | |||||
| static const ExtensibleWavSubFormat pcmFormat = { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; | |||||
| static const ExtensibleWavSubFormat IEEEFloatFormat = { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; | |||||
| static const ExtensibleWavSubFormat ambisonicFormat = { 0x00000001, 0x0721, 0x11d3, { 0x86, 0x44, 0xC8, 0xC1, 0xCA, 0x00, 0x00, 0x00 } }; | |||||
| struct DataSize64Chunk // chunk ID = 'ds64' if data size > 0xffffffff, 'JUNK' otherwise | |||||
| { | |||||
| uint32 riffSizeLow; // low 4 byte size of RF64 block | |||||
| uint32 riffSizeHigh; // high 4 byte size of RF64 block | |||||
| uint32 dataSizeLow; // low 4 byte size of data chunk | |||||
| uint32 dataSizeHigh; // high 4 byte size of data chunk | |||||
| uint32 sampleCountLow; // low 4 byte sample count of fact chunk | |||||
| uint32 sampleCountHigh; // high 4 byte sample count of fact chunk | |||||
| uint32 tableLength; // number of valid entries in array 'table' | |||||
| } WATER_PACKED; | |||||
| #if _MSVC_VER | |||||
| #pragma pack (pop) | |||||
| #endif | |||||
| } | |||||
| //============================================================================== | |||||
| class WavAudioFormatReader : public AudioFormatReader | |||||
| { | |||||
| public: | |||||
| WavAudioFormatReader (InputStream* const in) | |||||
| : AudioFormatReader (in, wavFormatName), | |||||
| bwavChunkStart (0), | |||||
| bwavSize (0), | |||||
| dataLength (0), | |||||
| isRF64 (false) | |||||
| { | |||||
| using namespace WavFileHelpers; | |||||
| uint64 len = 0; | |||||
| uint64 end = 0; | |||||
| int cueNoteIndex = 0; | |||||
| int cueLabelIndex = 0; | |||||
| int cueRegionIndex = 0; | |||||
| const int firstChunkType = input->readInt(); | |||||
| if (firstChunkType == chunkName ("RF64")) | |||||
| { | |||||
| input->skipNextBytes (4); // size is -1 for RF64 | |||||
| isRF64 = true; | |||||
| } | |||||
| else if (firstChunkType == chunkName ("RIFF")) | |||||
| { | |||||
| len = (uint64) (uint32) input->readInt(); | |||||
| end = len + (uint64) input->getPosition(); | |||||
| } | |||||
| else | |||||
| { | |||||
| return; | |||||
| } | |||||
| const int64 startOfRIFFChunk = input->getPosition(); | |||||
| if (input->readInt() == chunkName ("WAVE")) | |||||
| { | |||||
| if (isRF64 && input->readInt() == chunkName ("ds64")) | |||||
| { | |||||
| const uint32 length = (uint32) input->readInt(); | |||||
| if (length < 28) | |||||
| return; | |||||
| const int64 chunkEnd = input->getPosition() + length + (length & 1); | |||||
| len = (uint64) input->readInt64(); | |||||
| end = len + (uint64) startOfRIFFChunk; | |||||
| dataLength = input->readInt64(); | |||||
| input->setPosition (chunkEnd); | |||||
| } | |||||
| while ((uint64) input->getPosition() < end && ! input->isExhausted()) | |||||
| { | |||||
| const int chunkType = input->readInt(); | |||||
| uint32 length = (uint32) input->readInt(); | |||||
| const int64 chunkEnd = input->getPosition() + length + (length & 1); | |||||
| if (chunkType == chunkName ("fmt ")) | |||||
| { | |||||
| // read the format chunk | |||||
| const unsigned short format = (unsigned short) input->readShort(); | |||||
| numChannels = (unsigned int) input->readShort(); | |||||
| sampleRate = input->readInt(); | |||||
| const int bytesPerSec = input->readInt(); | |||||
| input->skipNextBytes (2); | |||||
| bitsPerSample = (unsigned int) (int) input->readShort(); | |||||
| if (bitsPerSample > 64) | |||||
| { | |||||
| bytesPerFrame = bytesPerSec / (int) sampleRate; | |||||
| bitsPerSample = 8 * (unsigned int) bytesPerFrame / numChannels; | |||||
| } | |||||
| else | |||||
| { | |||||
| bytesPerFrame = numChannels * bitsPerSample / 8; | |||||
| } | |||||
| if (format == 3) | |||||
| { | |||||
| usesFloatingPointData = true; | |||||
| } | |||||
| else if (format == 0xfffe /*WAVE_FORMAT_EXTENSIBLE*/) | |||||
| { | |||||
| if (length < 40) // too short | |||||
| { | |||||
| bytesPerFrame = 0; | |||||
| } | |||||
| else | |||||
| { | |||||
| input->skipNextBytes (4); // skip over size and bitsPerSample | |||||
| metadataValues.set ("ChannelMask", String (input->readInt())); | |||||
| ExtensibleWavSubFormat subFormat; | |||||
| subFormat.data1 = (uint32) input->readInt(); | |||||
| subFormat.data2 = (uint16) input->readShort(); | |||||
| subFormat.data3 = (uint16) input->readShort(); | |||||
| input->read (subFormat.data4, sizeof (subFormat.data4)); | |||||
| if (subFormat == IEEEFloatFormat) | |||||
| usesFloatingPointData = true; | |||||
| else if (subFormat != pcmFormat && subFormat != ambisonicFormat) | |||||
| bytesPerFrame = 0; | |||||
| } | |||||
| } | |||||
| else if (format != 1) | |||||
| { | |||||
| bytesPerFrame = 0; | |||||
| } | |||||
| } | |||||
| else if (chunkType == chunkName ("data")) | |||||
| { | |||||
| if (! isRF64) // data size is expected to be -1, actual data size is in ds64 chunk | |||||
| dataLength = length; | |||||
| dataChunkStart = input->getPosition(); | |||||
| lengthInSamples = (bytesPerFrame > 0) ? (dataLength / bytesPerFrame) : 0; | |||||
| } | |||||
| else if (chunkType == chunkName ("bext")) | |||||
| { | |||||
| bwavChunkStart = input->getPosition(); | |||||
| bwavSize = length; | |||||
| 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; | |||||
| 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; | |||||
| 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; | |||||
| cue.calloc (jmax ((size_t) length + 1, sizeof (CueChunk)), 1); | |||||
| input->read (cue, (int) length); | |||||
| cue->copyTo (metadataValues, (int) length); | |||||
| } | |||||
| else if (chunkType == chunkName ("axml")) | |||||
| { | |||||
| MemoryBlock axml; | |||||
| input->readIntoMemoryBlock (axml, (ssize_t) length); | |||||
| AXMLChunk::addToMetadata (metadataValues, axml.toString()); | |||||
| } | |||||
| else if (chunkType == chunkName ("LIST")) | |||||
| { | |||||
| 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) | |||||
| { | |||||
| const int adtlChunkType = input->readInt(); | |||||
| const uint32 adtlLength = (uint32) input->readInt(); | |||||
| const int64 adtlChunkEnd = input->getPosition() + (adtlLength + (adtlLength & 1)); | |||||
| if (adtlChunkType == chunkName ("labl") || adtlChunkType == chunkName ("note")) | |||||
| { | |||||
| String prefix; | |||||
| if (adtlChunkType == chunkName ("labl")) | |||||
| prefix << "CueLabel" << cueLabelIndex++; | |||||
| else if (adtlChunkType == chunkName ("note")) | |||||
| prefix << "CueNote" << cueNoteIndex++; | |||||
| const uint32 identifier = (uint32) input->readInt(); | |||||
| const int stringLength = (int) adtlLength - 4; | |||||
| MemoryBlock textBlock; | |||||
| input->readIntoMemoryBlock (textBlock, stringLength); | |||||
| metadataValues.set (prefix + "Identifier", String (identifier)); | |||||
| metadataValues.set (prefix + "Text", textBlock.toString()); | |||||
| } | |||||
| else if (adtlChunkType == chunkName ("ltxt")) | |||||
| { | |||||
| const String prefix ("CueRegion" + String (cueRegionIndex++)); | |||||
| const uint32 identifier = (uint32) input->readInt(); | |||||
| const uint32 sampleLength = (uint32) input->readInt(); | |||||
| const uint32 purpose = (uint32) input->readInt(); | |||||
| const uint16 country = (uint16) input->readInt(); | |||||
| const uint16 language = (uint16) input->readInt(); | |||||
| const uint16 dialect = (uint16) input->readInt(); | |||||
| const uint16 codePage = (uint16) input->readInt(); | |||||
| const uint32 stringLength = adtlLength - 20; | |||||
| MemoryBlock textBlock; | |||||
| input->readIntoMemoryBlock (textBlock, (int) stringLength); | |||||
| metadataValues.set (prefix + "Identifier", String (identifier)); | |||||
| metadataValues.set (prefix + "SampleLength", String (sampleLength)); | |||||
| metadataValues.set (prefix + "Purpose", String (purpose)); | |||||
| metadataValues.set (prefix + "Country", String (country)); | |||||
| metadataValues.set (prefix + "Language", String (language)); | |||||
| metadataValues.set (prefix + "Dialect", String (dialect)); | |||||
| metadataValues.set (prefix + "CodePage", String (codePage)); | |||||
| metadataValues.set (prefix + "Text", textBlock.toString()); | |||||
| } | |||||
| input->setPosition (adtlChunkEnd); | |||||
| } | |||||
| } | |||||
| } | |||||
| else if (chunkType == chunkName ("acid")) | |||||
| { | |||||
| AcidChunk (*input, length).addToMetadata (metadataValues); | |||||
| } | |||||
| else if (chunkType == chunkName ("Trkn")) | |||||
| { | |||||
| MemoryBlock tracktion; | |||||
| input->readIntoMemoryBlock (tracktion, (ssize_t) length); | |||||
| metadataValues.set (WavAudioFormat::tracktionLoopInfo, tracktion.toString()); | |||||
| } | |||||
| else if (chunkEnd <= input->getPosition()) | |||||
| { | |||||
| break; | |||||
| } | |||||
| input->setPosition (chunkEnd); | |||||
| } | |||||
| } | |||||
| if (cueLabelIndex > 0) metadataValues.set ("NumCueLabels", String (cueLabelIndex)); | |||||
| if (cueNoteIndex > 0) metadataValues.set ("NumCueNotes", String (cueNoteIndex)); | |||||
| if (cueRegionIndex > 0) metadataValues.set ("NumCueRegions", String (cueRegionIndex)); | |||||
| if (metadataValues.size() > 0) metadataValues.set ("MetaDataSource", "WAV"); | |||||
| } | |||||
| //============================================================================== | |||||
| bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | |||||
| int64 startSampleInFile, int numSamples) override | |||||
| { | |||||
| clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer, | |||||
| startSampleInFile, numSamples, lengthInSamples); | |||||
| if (numSamples <= 0) | |||||
| return true; | |||||
| input->setPosition (dataChunkStart + startSampleInFile * bytesPerFrame); | |||||
| while (numSamples > 0) | |||||
| { | |||||
| const int tempBufSize = 480 * 3 * 4; // (keep this a multiple of 3) | |||||
| char tempBuffer [tempBufSize]; | |||||
| const int numThisTime = jmin (tempBufSize / bytesPerFrame, numSamples); | |||||
| const int bytesRead = input->read (tempBuffer, numThisTime * bytesPerFrame); | |||||
| if (bytesRead < numThisTime * bytesPerFrame) | |||||
| { | |||||
| jassert (bytesRead >= 0); | |||||
| zeromem (tempBuffer + bytesRead, (size_t) (numThisTime * bytesPerFrame - bytesRead)); | |||||
| } | |||||
| copySampleData (bitsPerSample, usesFloatingPointData, | |||||
| destSamples, startOffsetInDestBuffer, numDestChannels, | |||||
| tempBuffer, (int) numChannels, numThisTime); | |||||
| startOffsetInDestBuffer += numThisTime; | |||||
| numSamples -= numThisTime; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| static void copySampleData (unsigned int bitsPerSample, const bool usesFloatingPointData, | |||||
| int* const* destSamples, int startOffsetInDestBuffer, int numDestChannels, | |||||
| const void* sourceData, int numChannels, int numSamples) noexcept | |||||
| { | |||||
| switch (bitsPerSample) | |||||
| { | |||||
| case 8: | |||||
| ReadHelper<AudioData::Int32, AudioData::UInt8, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); | |||||
| break; | |||||
| case 16: | |||||
| ReadHelper<AudioData::Int32, AudioData::Int16, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); | |||||
| break; | |||||
| case 24: | |||||
| ReadHelper<AudioData::Int32, AudioData::Int24, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); | |||||
| break; | |||||
| case 32: | |||||
| if (usesFloatingPointData) | |||||
| ReadHelper<AudioData::Float32, AudioData::Float32, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); | |||||
| else | |||||
| ReadHelper<AudioData::Int32, AudioData::Int32, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); | |||||
| break; | |||||
| default: | |||||
| jassertfalse; | |||||
| break; | |||||
| } | |||||
| } | |||||
| int64 bwavChunkStart, bwavSize; | |||||
| int64 dataChunkStart, dataLength; | |||||
| int bytesPerFrame; | |||||
| bool isRF64; | |||||
| private: | |||||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatReader) | |||||
| }; | |||||
| //============================================================================== | |||||
| WavAudioFormat::WavAudioFormat() : AudioFormat (wavFormatName, ".wav .bwf") {} | |||||
| WavAudioFormat::~WavAudioFormat() {} | |||||
| Array<int> WavAudioFormat::getPossibleSampleRates() | |||||
| { | |||||
| const int rates[] = { 8000, 11025, 12000, 16000, 22050, 32000, 44100, | |||||
| 48000, 88200, 96000, 176400, 192000, 352800, 384000 }; | |||||
| return Array<int> (rates, numElementsInArray (rates)); | |||||
| } | |||||
| Array<int> WavAudioFormat::getPossibleBitDepths() | |||||
| { | |||||
| const int depths[] = { 8, 16, 24, 32 }; | |||||
| return Array<int> (depths, numElementsInArray (depths)); | |||||
| } | |||||
| bool WavAudioFormat::canDoStereo() { return true; } | |||||
| bool WavAudioFormat::canDoMono() { return true; } | |||||
| AudioFormatReader* WavAudioFormat::createReaderFor (InputStream* sourceStream, | |||||
| const bool deleteStreamIfOpeningFails) | |||||
| { | |||||
| ScopedPointer<WavAudioFormatReader> r (new WavAudioFormatReader (sourceStream)); | |||||
| if (r->sampleRate > 0 && r->numChannels > 0 && r->bytesPerFrame > 0) | |||||
| return r.release(); | |||||
| if (! deleteStreamIfOpeningFails) | |||||
| r->input = nullptr; | |||||
| return nullptr; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,200 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the Water library. | |||||
| Copyright (c) 2015 ROLI Ltd. | |||||
| Copyright (C) 2018 Filipe Coelho <falktx@falktx.com> | |||||
| Permission is granted to use this software under the terms of either: | |||||
| a) the GPL v2 (or any later version) | |||||
| b) the Affero GPL v3 | |||||
| Details of these licenses can be found at: www.gnu.org/licenses | |||||
| Water is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
| ============================================================================== | |||||
| */ | |||||
| #ifndef WATER_WAV_AUDIOFORMAT_H_INCLUDED | |||||
| #define WATER_WAV_AUDIOFORMAT_H_INCLUDED | |||||
| #include "../audioformat/AudioFormat.h" | |||||
| #include "../containers/Array.h" | |||||
| #include "CarlaJuceUtils.hpp" | |||||
| namespace water { | |||||
| //============================================================================== | |||||
| /** | |||||
| Reads and Writes WAV format audio files. | |||||
| @see AudioFormat | |||||
| */ | |||||
| class WavAudioFormat : public AudioFormat | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Creates a format object. */ | |||||
| WavAudioFormat(); | |||||
| /** Destructor. */ | |||||
| ~WavAudioFormat(); | |||||
| //============================================================================== | |||||
| // BWAV chunk properties: | |||||
| 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 | |||||
| it is.. | |||||
| @see AudioFormatReader::metadataValues, createWriterFor | |||||
| */ | |||||
| static const char* const bwavTimeReference; | |||||
| #if 0 | |||||
| /** 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 | |||||
| fills out the time and date in the right format. | |||||
| */ | |||||
| static StringPairArray createBWAVMetadata (const String& description, | |||||
| const String& originator, | |||||
| const String& originatorRef, | |||||
| Time dateAndTime, | |||||
| int64 timeReferenceSamples, | |||||
| const String& codingHistory); | |||||
| #endif | |||||
| //============================================================================== | |||||
| // '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. */ | |||||
| static const char* const ISRC; | |||||
| /** Metadata property name used when reading a WAV file with a Tracktion chunk. */ | |||||
| static const char* const tracktionLoopInfo; | |||||
| //============================================================================== | |||||
| Array<int> getPossibleSampleRates() override; | |||||
| Array<int> getPossibleBitDepths() override; | |||||
| bool canDoStereo() override; | |||||
| bool canDoMono() override; | |||||
| //============================================================================== | |||||
| AudioFormatReader* createReaderFor (InputStream* sourceStream, | |||||
| bool deleteStreamIfOpeningFails) override; | |||||
| private: | |||||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormat) | |||||
| }; | |||||
| } | |||||
| #endif // WATER_WAV_AUDIOFORMAT_H_INCLUDED | |||||
| @@ -0,0 +1,67 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the Water library. | |||||
| Copyright (c) 2015 ROLI Ltd. | |||||
| Copyright (C) 2018 Filipe Coelho <falktx@falktx.com> | |||||
| Permission is granted to use this software under the terms of either: | |||||
| a) the GPL v2 (or any later version) | |||||
| b) the Affero GPL v3 | |||||
| Details of these licenses can be found at: www.gnu.org/licenses | |||||
| Water is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
| ============================================================================== | |||||
| */ | |||||
| #include "AudioFormat.h" | |||||
| #include "../files/File.h" | |||||
| namespace water { | |||||
| AudioFormat::AudioFormat (String name, StringArray extensions) | |||||
| : formatName (name), fileExtensions (extensions) | |||||
| { | |||||
| } | |||||
| AudioFormat::AudioFormat (StringRef name, StringRef extensions) | |||||
| : formatName (name.text), fileExtensions (StringArray::fromTokens (extensions, false)) | |||||
| { | |||||
| } | |||||
| AudioFormat::~AudioFormat() | |||||
| { | |||||
| } | |||||
| bool AudioFormat::canHandleFile (const File& f) | |||||
| { | |||||
| for (int i = 0; i < fileExtensions.size(); ++i) | |||||
| if (f.hasFileExtension (fileExtensions[i])) | |||||
| return true; | |||||
| return false; | |||||
| } | |||||
| const String& AudioFormat::getFormatName() const { return formatName; } | |||||
| const StringArray& AudioFormat::getFileExtensions() const { return fileExtensions; } | |||||
| bool AudioFormat::isCompressed() { return false; } | |||||
| StringArray AudioFormat::getQualityOptions() { return StringArray(); } | |||||
| #if 0 | |||||
| MemoryMappedAudioFormatReader* AudioFormat::createMemoryMappedReader (const File&) | |||||
| { | |||||
| return nullptr; | |||||
| } | |||||
| MemoryMappedAudioFormatReader* AudioFormat::createMemoryMappedReader (FileInputStream* fin) | |||||
| { | |||||
| delete fin; | |||||
| return nullptr; | |||||
| } | |||||
| #endif | |||||
| } | |||||
| @@ -0,0 +1,134 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the Water library. | |||||
| Copyright (c) 2015 ROLI Ltd. | |||||
| Copyright (C) 2018 Filipe Coelho <falktx@falktx.com> | |||||
| Permission is granted to use this software under the terms of either: | |||||
| a) the GPL v2 (or any later version) | |||||
| b) the Affero GPL v3 | |||||
| Details of these licenses can be found at: www.gnu.org/licenses | |||||
| Water is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
| ============================================================================== | |||||
| */ | |||||
| #ifndef WATER_AUDIOFORMAT_H_INCLUDED | |||||
| #define WATER_AUDIOFORMAT_H_INCLUDED | |||||
| #include "../text/StringArray.h" | |||||
| namespace water { | |||||
| //============================================================================== | |||||
| /** | |||||
| Subclasses of AudioFormat are used to read and write different audio | |||||
| file formats. | |||||
| @see AudioFormatReader, AudioFormatWriter, WavAudioFormat, AiffAudioFormat | |||||
| */ | |||||
| class AudioFormat | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Destructor. */ | |||||
| virtual ~AudioFormat(); | |||||
| //============================================================================== | |||||
| /** Returns the name of this format. | |||||
| e.g. "WAV file" or "AIFF file" | |||||
| */ | |||||
| const String& getFormatName() const; | |||||
| /** Returns all the file extensions that might apply to a file of this format. | |||||
| The first item will be the one that's preferred when creating a new file. | |||||
| So for a wav file this might just return ".wav"; for an AIFF file it might | |||||
| return two items, ".aif" and ".aiff" | |||||
| */ | |||||
| const StringArray& getFileExtensions() const; | |||||
| //============================================================================== | |||||
| /** Returns true if this the given file can be read by this format. | |||||
| Subclasses shouldn't do too much work here, just check the extension or | |||||
| file type. The base class implementation just checks the file's extension | |||||
| against one of the ones that was registered in the constructor. | |||||
| */ | |||||
| virtual bool canHandleFile (const File& fileToTest); | |||||
| /** Returns a set of sample rates that the format can read and write. */ | |||||
| virtual Array<int> getPossibleSampleRates() = 0; | |||||
| /** Returns a set of bit depths that the format can read and write. */ | |||||
| virtual Array<int> getPossibleBitDepths() = 0; | |||||
| /** Returns true if the format can do 2-channel audio. */ | |||||
| virtual bool canDoStereo() = 0; | |||||
| /** Returns true if the format can do 1-channel audio. */ | |||||
| virtual bool canDoMono() = 0; | |||||
| /** Returns true if the format uses compressed data. */ | |||||
| virtual bool isCompressed(); | |||||
| /** Returns a list of different qualities that can be used when writing. | |||||
| Non-compressed formats will just return an empty array, but for something | |||||
| like Ogg-Vorbis or MP3, it might return a list of bit-rates, etc. | |||||
| When calling createWriterFor(), an index from this array is passed in to | |||||
| tell the format which option is required. | |||||
| */ | |||||
| virtual StringArray getQualityOptions(); | |||||
| //============================================================================== | |||||
| /** Tries to create an object that can read from a stream containing audio | |||||
| data in this format. | |||||
| The reader object that is returned can be used to read from the stream, and | |||||
| should then be deleted by the caller. | |||||
| @param sourceStream the stream to read from - the AudioFormatReader object | |||||
| that is returned will delete this stream when it no longer | |||||
| needs it. | |||||
| @param deleteStreamIfOpeningFails if no reader can be created, this determines whether this method | |||||
| should delete the stream object that was passed-in. (If a valid | |||||
| reader is returned, it will always be in charge of deleting the | |||||
| stream, so this parameter is ignored) | |||||
| @see AudioFormatReader | |||||
| */ | |||||
| virtual AudioFormatReader* createReaderFor (InputStream* sourceStream, | |||||
| bool deleteStreamIfOpeningFails) = 0; | |||||
| protected: | |||||
| /** Creates an AudioFormat object. | |||||
| @param formatName this sets the value that will be returned by getFormatName() | |||||
| @param fileExtensions an array of file extensions - these will be returned by getFileExtensions() | |||||
| */ | |||||
| AudioFormat (String formatName, StringArray fileExtensions); | |||||
| /** Creates an AudioFormat object. | |||||
| @param formatName this sets the value that will be returned by getFormatName() | |||||
| @param fileExtensions a whitespace-separated list of file extensions - these will | |||||
| be returned by getFileExtensions() | |||||
| */ | |||||
| AudioFormat (StringRef formatName, StringRef fileExtensions); | |||||
| private: | |||||
| //============================================================================== | |||||
| String formatName; | |||||
| StringArray fileExtensions; | |||||
| }; | |||||
| } | |||||
| #endif // WATER_AUDIOFORMAT_H_INCLUDED | |||||
| @@ -0,0 +1,184 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the Water library. | |||||
| Copyright (c) 2015 ROLI Ltd. | |||||
| Copyright (C) 2018 Filipe Coelho <falktx@falktx.com> | |||||
| Permission is granted to use this software under the terms of either: | |||||
| a) the GPL v2 (or any later version) | |||||
| b) the Affero GPL v3 | |||||
| Details of these licenses can be found at: www.gnu.org/licenses | |||||
| Water is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
| ============================================================================== | |||||
| */ | |||||
| #include "AudioFormatManager.h" | |||||
| #include "../audiocodecs/WavAudioFormat.h" | |||||
| #include "../files/FileInputStream.h" | |||||
| namespace water { | |||||
| AudioFormatManager::AudioFormatManager() : defaultFormatIndex (0) {} | |||||
| AudioFormatManager::~AudioFormatManager() {} | |||||
| //============================================================================== | |||||
| void AudioFormatManager::registerFormat (AudioFormat* newFormat, const bool makeThisTheDefaultFormat) | |||||
| { | |||||
| jassert (newFormat != nullptr); | |||||
| if (newFormat != nullptr) | |||||
| { | |||||
| #if DEBUG | |||||
| for (int i = getNumKnownFormats(); --i >= 0;) | |||||
| { | |||||
| if (getKnownFormat (i)->getFormatName() == newFormat->getFormatName()) | |||||
| { | |||||
| jassertfalse; // trying to add the same format twice! | |||||
| } | |||||
| } | |||||
| #endif | |||||
| if (makeThisTheDefaultFormat) | |||||
| defaultFormatIndex = getNumKnownFormats(); | |||||
| knownFormats.add (newFormat); | |||||
| } | |||||
| } | |||||
| void AudioFormatManager::registerBasicFormats() | |||||
| { | |||||
| registerFormat (new WavAudioFormat(), true); | |||||
| #if 0 | |||||
| registerFormat (new AiffAudioFormat(), false); | |||||
| #endif | |||||
| #if WATER_USE_FLAC | |||||
| registerFormat (new FlacAudioFormat(), false); | |||||
| #endif | |||||
| #if WATER_USE_OGGVORBIS | |||||
| registerFormat (new OggVorbisAudioFormat(), false); | |||||
| #endif | |||||
| #if WATER_MAC || WATER_IOS | |||||
| registerFormat (new CoreAudioFormat(), false); | |||||
| #endif | |||||
| #if WATER_USE_MP3AUDIOFORMAT | |||||
| registerFormat (new MP3AudioFormat(), false); | |||||
| #endif | |||||
| #if WATER_USE_WINDOWS_MEDIA_FORMAT | |||||
| registerFormat (new WindowsMediaAudioFormat(), false); | |||||
| #endif | |||||
| } | |||||
| void AudioFormatManager::clearFormats() | |||||
| { | |||||
| knownFormats.clear(); | |||||
| defaultFormatIndex = 0; | |||||
| } | |||||
| int AudioFormatManager::getNumKnownFormats() const | |||||
| { | |||||
| return knownFormats.size(); | |||||
| } | |||||
| AudioFormat* AudioFormatManager::getKnownFormat (const int index) const | |||||
| { | |||||
| return knownFormats [index]; | |||||
| } | |||||
| AudioFormat* AudioFormatManager::getDefaultFormat() const | |||||
| { | |||||
| return getKnownFormat (defaultFormatIndex); | |||||
| } | |||||
| AudioFormat* AudioFormatManager::findFormatForFileExtension (const String& fileExtension) const | |||||
| { | |||||
| if (! fileExtension.startsWithChar ('.')) | |||||
| return findFormatForFileExtension ("." + fileExtension); | |||||
| for (int i = 0; i < getNumKnownFormats(); ++i) | |||||
| if (getKnownFormat(i)->getFileExtensions().contains (fileExtension, true)) | |||||
| return getKnownFormat(i); | |||||
| return nullptr; | |||||
| } | |||||
| String AudioFormatManager::getWildcardForAllFormats() const | |||||
| { | |||||
| StringArray extensions; | |||||
| for (int i = 0; i < getNumKnownFormats(); ++i) | |||||
| extensions.addArray (getKnownFormat(i)->getFileExtensions()); | |||||
| extensions.trim(); | |||||
| extensions.removeEmptyStrings(); | |||||
| for (int i = 0; i < extensions.size(); ++i) | |||||
| extensions.set (i, (extensions[i].startsWithChar ('.') ? "*" : "*.") + extensions[i]); | |||||
| extensions.removeDuplicates (true); | |||||
| return extensions.joinIntoString (";"); | |||||
| } | |||||
| //============================================================================== | |||||
| AudioFormatReader* AudioFormatManager::createReaderFor (const File& file) | |||||
| { | |||||
| // you need to actually register some formats before the manager can | |||||
| // use them to open a file! | |||||
| jassert (getNumKnownFormats() > 0); | |||||
| for (int i = 0; i < getNumKnownFormats(); ++i) | |||||
| { | |||||
| AudioFormat* const af = getKnownFormat(i); | |||||
| if (af->canHandleFile (file)) | |||||
| if (InputStream* const in = file.createInputStream()) | |||||
| if (AudioFormatReader* const r = af->createReaderFor (in, true)) | |||||
| return r; | |||||
| } | |||||
| return nullptr; | |||||
| } | |||||
| AudioFormatReader* AudioFormatManager::createReaderFor (InputStream* audioFileStream) | |||||
| { | |||||
| // you need to actually register some formats before the manager can | |||||
| // use them to open a file! | |||||
| jassert (getNumKnownFormats() > 0); | |||||
| ScopedPointer<InputStream> in (audioFileStream); | |||||
| if (in != nullptr) | |||||
| { | |||||
| const int64 originalStreamPos = in->getPosition(); | |||||
| for (int i = 0; i < getNumKnownFormats(); ++i) | |||||
| { | |||||
| if (AudioFormatReader* const r = getKnownFormat(i)->createReaderFor (in, false)) | |||||
| { | |||||
| in.release(); | |||||
| return r; | |||||
| } | |||||
| in->setPosition (originalStreamPos); | |||||
| // the stream that is passed-in must be capable of being repositioned so | |||||
| // that all the formats can have a go at opening it. | |||||
| jassert (in->getPosition() == originalStreamPos); | |||||
| } | |||||
| } | |||||
| return nullptr; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,147 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the Water library. | |||||
| Copyright (c) 2015 ROLI Ltd. | |||||
| Copyright (C) 2018 Filipe Coelho <falktx@falktx.com> | |||||
| Permission is granted to use this software under the terms of either: | |||||
| a) the GPL v2 (or any later version) | |||||
| b) the Affero GPL v3 | |||||
| Details of these licenses can be found at: www.gnu.org/licenses | |||||
| Water is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
| ============================================================================== | |||||
| */ | |||||
| #ifndef WATER_AUDIOFORMATMANAGER_H_INCLUDED | |||||
| #define WATER_AUDIOFORMATMANAGER_H_INCLUDED | |||||
| #include "AudioFormat.h" | |||||
| #include "../containers/OwnedArray.h" | |||||
| #include "../text/String.h" | |||||
| #include "CarlaJuceUtils.hpp" | |||||
| namespace water { | |||||
| //============================================================================== | |||||
| /** | |||||
| A class for keeping a list of available audio formats, and for deciding which | |||||
| one to use to open a given file. | |||||
| After creating an AudioFormatManager object, you should call registerFormat() | |||||
| or registerBasicFormats() to give it a list of format types that it can use. | |||||
| @see AudioFormat | |||||
| */ | |||||
| class AudioFormatManager | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Creates an empty format manager. | |||||
| Before it'll be any use, you'll need to call registerFormat() with all the | |||||
| formats you want it to be able to recognise. | |||||
| */ | |||||
| AudioFormatManager(); | |||||
| /** Destructor. */ | |||||
| ~AudioFormatManager(); | |||||
| //============================================================================== | |||||
| /** Adds a format to the manager's list of available file types. | |||||
| The object passed-in will be deleted by this object, so don't keep a pointer | |||||
| to it! | |||||
| If makeThisTheDefaultFormat is true, then the getDefaultFormat() method will | |||||
| return this one when called. | |||||
| */ | |||||
| void registerFormat (AudioFormat* newFormat, | |||||
| bool makeThisTheDefaultFormat); | |||||
| /** Handy method to make it easy to register the formats that come with Water. | |||||
| Currently, this will add WAV and AIFF to the list. | |||||
| */ | |||||
| void registerBasicFormats(); | |||||
| /** Clears the list of known formats. */ | |||||
| void clearFormats(); | |||||
| /** Returns the number of currently registered file formats. */ | |||||
| int getNumKnownFormats() const; | |||||
| /** Returns one of the registered file formats. */ | |||||
| AudioFormat* getKnownFormat (int index) const; | |||||
| /** Iterator access to the list of known formats. */ | |||||
| AudioFormat** begin() const noexcept { return knownFormats.begin(); } | |||||
| /** Iterator access to the list of known formats. */ | |||||
| AudioFormat** end() const noexcept { return knownFormats.end(); } | |||||
| /** Looks for which of the known formats is listed as being for a given file | |||||
| extension. | |||||
| The extension may have a dot before it, so e.g. ".wav" or "wav" are both ok. | |||||
| */ | |||||
| AudioFormat* findFormatForFileExtension (const String& fileExtension) const; | |||||
| /** Returns the format which has been set as the default one. | |||||
| You can set a format as being the default when it is registered. It's useful | |||||
| when you want to write to a file, because the best format may change between | |||||
| platforms, e.g. AIFF is preferred on the Mac, WAV on Windows. | |||||
| If none has been set as the default, this method will just return the first | |||||
| one in the list. | |||||
| */ | |||||
| AudioFormat* getDefaultFormat() const; | |||||
| /** Returns a set of wildcards for file-matching that contains the extensions for | |||||
| all known formats. | |||||
| E.g. if might return "*.wav;*.aiff" if it just knows about wavs and aiffs. | |||||
| */ | |||||
| String getWildcardForAllFormats() const; | |||||
| //============================================================================== | |||||
| /** Searches through the known formats to try to create a suitable reader for | |||||
| this file. | |||||
| If none of the registered formats can open the file, it'll return 0. If it | |||||
| returns a reader, it's the caller's responsibility to delete the reader. | |||||
| */ | |||||
| AudioFormatReader* createReaderFor (const File& audioFile); | |||||
| /** Searches through the known formats to try to create a suitable reader for | |||||
| this stream. | |||||
| The stream object that is passed-in will be deleted by this method or by the | |||||
| reader that is returned, so the caller should not keep any references to it. | |||||
| The stream that is passed-in must be capable of being repositioned so | |||||
| that all the formats can have a go at opening it. | |||||
| If none of the registered formats can open the stream, it'll return nullptr. | |||||
| If it returns a reader, it's the caller's responsibility to delete the reader. | |||||
| */ | |||||
| AudioFormatReader* createReaderFor (InputStream* audioFileStream); | |||||
| private: | |||||
| //============================================================================== | |||||
| OwnedArray<AudioFormat> knownFormats; | |||||
| int defaultFormatIndex; | |||||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioFormatManager) | |||||
| }; | |||||
| } | |||||
| #endif // WATER_AUDIOFORMATMANAGER_H_INCLUDED | |||||
| @@ -0,0 +1,191 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the Water library. | |||||
| Copyright (c) 2015 ROLI Ltd. | |||||
| Copyright (C) 2018 Filipe Coelho <falktx@falktx.com> | |||||
| Permission is granted to use this software under the terms of either: | |||||
| a) the GPL v2 (or any later version) | |||||
| b) the Affero GPL v3 | |||||
| Details of these licenses can be found at: www.gnu.org/licenses | |||||
| Water is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
| ============================================================================== | |||||
| */ | |||||
| #include "AudioFormatReader.h" | |||||
| #include "../buffers/AudioSampleBuffer.h" | |||||
| #include "../streams/InputStream.h" | |||||
| namespace water { | |||||
| AudioFormatReader::AudioFormatReader (InputStream* const in, const String& name) | |||||
| : sampleRate (0), | |||||
| bitsPerSample (0), | |||||
| lengthInSamples (0), | |||||
| numChannels (0), | |||||
| usesFloatingPointData (false), | |||||
| input (in), | |||||
| formatName (name) | |||||
| { | |||||
| } | |||||
| AudioFormatReader::~AudioFormatReader() | |||||
| { | |||||
| delete input; | |||||
| } | |||||
| bool AudioFormatReader::read (int* const* destSamples, | |||||
| int numDestChannels, | |||||
| int64 startSampleInSource, | |||||
| int numSamplesToRead, | |||||
| const bool fillLeftoverChannelsWithCopies) | |||||
| { | |||||
| jassert (numDestChannels > 0); // you have to actually give this some channels to work with! | |||||
| const size_t originalNumSamplesToRead = (size_t) numSamplesToRead; | |||||
| int startOffsetInDestBuffer = 0; | |||||
| if (startSampleInSource < 0) | |||||
| { | |||||
| const int silence = (int) jmin (-startSampleInSource, (int64) numSamplesToRead); | |||||
| for (int i = numDestChannels; --i >= 0;) | |||||
| if (destSamples[i] != nullptr) | |||||
| zeromem (destSamples[i], sizeof (int) * (size_t) silence); | |||||
| startOffsetInDestBuffer += silence; | |||||
| numSamplesToRead -= silence; | |||||
| startSampleInSource = 0; | |||||
| } | |||||
| if (numSamplesToRead <= 0) | |||||
| return true; | |||||
| if (! readSamples (const_cast<int**> (destSamples), | |||||
| jmin ((int) numChannels, numDestChannels), startOffsetInDestBuffer, | |||||
| startSampleInSource, numSamplesToRead)) | |||||
| return false; | |||||
| if (numDestChannels > (int) numChannels) | |||||
| { | |||||
| if (fillLeftoverChannelsWithCopies) | |||||
| { | |||||
| int* lastFullChannel = destSamples[0]; | |||||
| for (int i = (int) numChannels; --i > 0;) | |||||
| { | |||||
| if (destSamples[i] != nullptr) | |||||
| { | |||||
| lastFullChannel = destSamples[i]; | |||||
| break; | |||||
| } | |||||
| } | |||||
| if (lastFullChannel != nullptr) | |||||
| for (int i = (int) numChannels; i < numDestChannels; ++i) | |||||
| if (destSamples[i] != nullptr) | |||||
| memcpy (destSamples[i], lastFullChannel, sizeof (int) * originalNumSamplesToRead); | |||||
| } | |||||
| else | |||||
| { | |||||
| for (int i = (int) numChannels; i < numDestChannels; ++i) | |||||
| if (destSamples[i] != nullptr) | |||||
| zeromem (destSamples[i], sizeof (int) * originalNumSamplesToRead); | |||||
| } | |||||
| } | |||||
| return true; | |||||
| } | |||||
| static void readChannels (AudioFormatReader& reader, | |||||
| int** const chans, AudioSampleBuffer* const buffer, | |||||
| const int startSample, const int numSamples, | |||||
| const int64 readerStartSample, const int numTargetChannels) | |||||
| { | |||||
| for (int j = 0; j < numTargetChannels; ++j) | |||||
| chans[j] = reinterpret_cast<int*> (buffer->getWritePointer (j, startSample)); | |||||
| chans[numTargetChannels] = nullptr; | |||||
| reader.read (chans, numTargetChannels, readerStartSample, numSamples, true); | |||||
| } | |||||
| #define JUCE_PERFORM_VEC_OP_SRC_DEST(normalOp, vecOp, locals, increment, setupOp) \ | |||||
| for (int i = 0; i < num; ++i) normalOp; | |||||
| static void convertFixedToFloat (float* dest, const int* src, float multiplier, int num) noexcept | |||||
| { | |||||
| for (int i = 0; i < num; ++i) | |||||
| dest[i] = src[i] * multiplier; | |||||
| } | |||||
| void AudioFormatReader::read (AudioSampleBuffer* buffer, | |||||
| int startSample, | |||||
| int numSamples, | |||||
| int64 readerStartSample, | |||||
| bool useReaderLeftChan, | |||||
| bool useReaderRightChan) | |||||
| { | |||||
| jassert (buffer != nullptr); | |||||
| jassert (startSample >= 0 && startSample + numSamples <= buffer->getNumSamples()); | |||||
| if (numSamples > 0) | |||||
| { | |||||
| const int numTargetChannels = buffer->getNumChannels(); | |||||
| if (numTargetChannels <= 2) | |||||
| { | |||||
| int* const dest0 = reinterpret_cast<int*> (buffer->getWritePointer (0, startSample)); | |||||
| int* const dest1 = reinterpret_cast<int*> (numTargetChannels > 1 ? buffer->getWritePointer (1, startSample) : nullptr); | |||||
| int* chans[3]; | |||||
| if (useReaderLeftChan == useReaderRightChan) | |||||
| { | |||||
| chans[0] = dest0; | |||||
| chans[1] = numChannels > 1 ? dest1 : nullptr; | |||||
| } | |||||
| else if (useReaderLeftChan || (numChannels == 1)) | |||||
| { | |||||
| chans[0] = dest0; | |||||
| chans[1] = nullptr; | |||||
| } | |||||
| else if (useReaderRightChan) | |||||
| { | |||||
| chans[0] = nullptr; | |||||
| chans[1] = dest0; | |||||
| } | |||||
| chans[2] = nullptr; | |||||
| read (chans, 2, readerStartSample, numSamples, true); | |||||
| // if the target's stereo and the source is mono, dupe the first channel.. | |||||
| if (numTargetChannels > 1 && (chans[0] == nullptr || chans[1] == nullptr)) | |||||
| memcpy (dest1, dest0, sizeof (float) * (size_t) numSamples); | |||||
| } | |||||
| else if (numTargetChannels <= 64) | |||||
| { | |||||
| int* chans[65]; | |||||
| readChannels (*this, chans, buffer, startSample, numSamples, readerStartSample, numTargetChannels); | |||||
| } | |||||
| else | |||||
| { | |||||
| HeapBlock<int*> chans; | |||||
| chans.malloc ((size_t) numTargetChannels + 1); | |||||
| // FIXME, check malloc return | |||||
| readChannels (*this, chans, buffer, startSample, numSamples, readerStartSample, numTargetChannels); | |||||
| } | |||||
| if (! usesFloatingPointData) | |||||
| for (int j = 0; j < numTargetChannels; ++j) | |||||
| if (float* const d = buffer->getWritePointer (j, startSample)) | |||||
| convertFixedToFloat (d, reinterpret_cast<const int*> (d), 1.0f / 0x7fffffff, numSamples); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,234 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the Water library. | |||||
| Copyright (c) 2015 ROLI Ltd. | |||||
| Copyright (C) 2018 Filipe Coelho <falktx@falktx.com> | |||||
| Permission is granted to use this software under the terms of either: | |||||
| a) the GPL v2 (or any later version) | |||||
| b) the Affero GPL v3 | |||||
| Details of these licenses can be found at: www.gnu.org/licenses | |||||
| Water is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
| ============================================================================== | |||||
| */ | |||||
| #ifndef WATER_AUDIOFORMATREADER_H_INCLUDED | |||||
| #define WATER_AUDIOFORMATREADER_H_INCLUDED | |||||
| #include "../buffers/AudioDataConverters.h" | |||||
| #include "../text/StringPairArray.h" | |||||
| #include "CarlaJuceUtils.hpp" | |||||
| namespace water { | |||||
| //============================================================================== | |||||
| /** | |||||
| Reads samples from an audio file stream. | |||||
| A subclass that reads a specific type of audio format will be created by | |||||
| an AudioFormat object. | |||||
| @see AudioFormat, AudioFormatWriter | |||||
| */ | |||||
| class AudioFormatReader | |||||
| { | |||||
| protected: | |||||
| //============================================================================== | |||||
| /** Creates an AudioFormatReader object. | |||||
| @param sourceStream the stream to read from - this will be deleted | |||||
| by this object when it is no longer needed. (Some | |||||
| specialised readers might not use this parameter and | |||||
| can leave it as nullptr). | |||||
| @param formatName the description that will be returned by the getFormatName() | |||||
| method | |||||
| */ | |||||
| AudioFormatReader (InputStream* sourceStream, | |||||
| const String& formatName); | |||||
| public: | |||||
| /** Destructor. */ | |||||
| virtual ~AudioFormatReader(); | |||||
| //============================================================================== | |||||
| /** Returns a description of what type of format this is. | |||||
| E.g. "AIFF" | |||||
| */ | |||||
| const String& getFormatName() const noexcept { return formatName; } | |||||
| //============================================================================== | |||||
| /** Reads samples from the stream. | |||||
| @param destSamples an array of buffers into which the sample data for each | |||||
| channel will be written. | |||||
| If the format is fixed-point, each channel will be written | |||||
| as an array of 32-bit signed integers using the full | |||||
| range -0x80000000 to 0x7fffffff, regardless of the source's | |||||
| bit-depth. If it is a floating-point format, you should cast | |||||
| the resulting array to a (float**) to get the values (in the | |||||
| range -1.0 to 1.0 or beyond) | |||||
| If the format is stereo, then destSamples[0] is the left channel | |||||
| data, and destSamples[1] is the right channel. | |||||
| The numDestChannels parameter indicates how many pointers this array | |||||
| contains, but some of these pointers can be null if you don't want to | |||||
| read data for some of the channels | |||||
| @param numDestChannels the number of array elements in the destChannels array | |||||
| @param startSampleInSource the position in the audio file or stream at which the samples | |||||
| should be read, as a number of samples from the start of the | |||||
| stream. It's ok for this to be beyond the start or end of the | |||||
| available data - any samples that are out-of-range will be returned | |||||
| as zeros. | |||||
| @param numSamplesToRead the number of samples to read. If this is greater than the number | |||||
| of samples that the file or stream contains. the result will be padded | |||||
| with zeros | |||||
| @param fillLeftoverChannelsWithCopies if true, this indicates that if there's no source data available | |||||
| for some of the channels that you pass in, then they should be filled with | |||||
| copies of valid source channels. | |||||
| E.g. if you're reading a mono file and you pass 2 channels to this method, then | |||||
| if fillLeftoverChannelsWithCopies is true, both destination channels will be filled | |||||
| with the same data from the file's single channel. If fillLeftoverChannelsWithCopies | |||||
| was false, then only the first channel would be filled with the file's contents, and | |||||
| the second would be cleared. If there are many channels, e.g. you try to read 4 channels | |||||
| from a stereo file, then the last 3 would all end up with copies of the same data. | |||||
| @returns true if the operation succeeded, false if there was an error. Note | |||||
| that reading sections of data beyond the extent of the stream isn't an | |||||
| error - the reader should just return zeros for these regions | |||||
| @see readMaxLevels | |||||
| */ | |||||
| bool read (int* const* destSamples, | |||||
| int numDestChannels, | |||||
| int64 startSampleInSource, | |||||
| int numSamplesToRead, | |||||
| bool fillLeftoverChannelsWithCopies); | |||||
| /** Fills a section of an AudioSampleBuffer from this reader. | |||||
| This will convert the reader's fixed- or floating-point data to | |||||
| the buffer's floating-point format, and will try to intelligently | |||||
| cope with mismatches between the number of channels in the reader | |||||
| and the buffer. | |||||
| */ | |||||
| void read (AudioSampleBuffer* buffer, | |||||
| int startSampleInDestBuffer, | |||||
| int numSamples, | |||||
| int64 readerStartSample, | |||||
| bool useReaderLeftChan, | |||||
| bool useReaderRightChan); | |||||
| //============================================================================== | |||||
| /** The sample-rate of the stream. */ | |||||
| double sampleRate; | |||||
| /** The number of bits per sample, e.g. 16, 24, 32. */ | |||||
| unsigned int bitsPerSample; | |||||
| /** The total number of samples in the audio stream. */ | |||||
| int64 lengthInSamples; | |||||
| /** The total number of channels in the audio stream. */ | |||||
| unsigned int numChannels; | |||||
| /** Indicates whether the data is floating-point or fixed. */ | |||||
| bool usesFloatingPointData; | |||||
| /** A set of metadata values that the reader has pulled out of the stream. | |||||
| Exactly what these values are depends on the format, so you can | |||||
| check out the format implementation code to see what kind of stuff | |||||
| they understand. | |||||
| */ | |||||
| StringPairArray metadataValues; | |||||
| /** The input stream, for use by subclasses. */ | |||||
| InputStream* input; | |||||
| //============================================================================== | |||||
| /** Subclasses must implement this method to perform the low-level read operation. | |||||
| Callers should use read() instead of calling this directly. | |||||
| @param destSamples the array of destination buffers to fill. Some of these | |||||
| pointers may be null | |||||
| @param numDestChannels the number of items in the destSamples array. This | |||||
| value is guaranteed not to be greater than the number of | |||||
| channels that this reader object contains | |||||
| @param startOffsetInDestBuffer the number of samples from the start of the | |||||
| dest data at which to begin writing | |||||
| @param startSampleInFile the number of samples into the source data at which | |||||
| to begin reading. This value is guaranteed to be >= 0. | |||||
| @param numSamples the number of samples to read | |||||
| */ | |||||
| virtual bool readSamples (int** destSamples, | |||||
| int numDestChannels, | |||||
| int startOffsetInDestBuffer, | |||||
| int64 startSampleInFile, | |||||
| int numSamples) = 0; | |||||
| protected: | |||||
| //============================================================================== | |||||
| /** Used by AudioFormatReader subclasses to copy data to different formats. */ | |||||
| template <class DestSampleType, class SourceSampleType, class SourceEndianness> | |||||
| struct ReadHelper | |||||
| { | |||||
| typedef AudioData::Pointer<DestSampleType, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst> DestType; | |||||
| typedef AudioData::Pointer<SourceSampleType, SourceEndianness, AudioData::Interleaved, AudioData::Const> SourceType; | |||||
| template <typename TargetType> | |||||
| static void read (TargetType* const* destData, int destOffset, int numDestChannels, | |||||
| const void* sourceData, int numSourceChannels, int numSamples) noexcept | |||||
| { | |||||
| for (int i = 0; i < numDestChannels; ++i) | |||||
| { | |||||
| if (void* targetChan = destData[i]) | |||||
| { | |||||
| DestType dest (targetChan); | |||||
| dest += destOffset; | |||||
| if (i < numSourceChannels) | |||||
| dest.convertSamples (SourceType (addBytesToPointer (sourceData, i * SourceType::getBytesPerSample()), numSourceChannels), numSamples); | |||||
| else | |||||
| dest.clearSamples (numSamples); | |||||
| } | |||||
| } | |||||
| } | |||||
| }; | |||||
| /** Used by AudioFormatReader subclasses to clear any parts of the data blocks that lie | |||||
| beyond the end of their available length. | |||||
| */ | |||||
| static void clearSamplesBeyondAvailableLength (int** destSamples, int numDestChannels, | |||||
| int startOffsetInDestBuffer, int64 startSampleInFile, | |||||
| int& numSamples, int64 fileLengthInSamples) | |||||
| { | |||||
| jassert (destSamples != nullptr); | |||||
| const int64 samplesAvailable = fileLengthInSamples - startSampleInFile; | |||||
| if (samplesAvailable < numSamples) | |||||
| { | |||||
| for (int i = numDestChannels; --i >= 0;) | |||||
| if (destSamples[i] != nullptr) | |||||
| zeromem (destSamples[i] + startOffsetInDestBuffer, sizeof (int) * (size_t) numSamples); | |||||
| numSamples = (int) samplesAvailable; | |||||
| } | |||||
| } | |||||
| private: | |||||
| String formatName; | |||||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioFormatReader) | |||||
| }; | |||||
| } | |||||
| #endif // WATER_AUDIOFORMATREADER_H_INCLUDED | |||||
| @@ -0,0 +1,478 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the Water library. | |||||
| Copyright (c) 2016 - ROLI Ltd. | |||||
| Copyright (C) 2018 Filipe Coelho <falktx@falktx.com> | |||||
| Permission is granted to use this software under the terms of the ISC license | |||||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
| Permission to use, copy, modify, and/or distribute this software for any | |||||
| purpose with or without fee is hereby granted, provided that the above | |||||
| copyright notice and this permission notice appear in all copies. | |||||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
| OF THIS SOFTWARE. | |||||
| ============================================================================== | |||||
| */ | |||||
| #include "AudioDataConverters.h" | |||||
| namespace water { | |||||
| void AudioDataConverters::convertFloatToInt16LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) | |||||
| { | |||||
| const double maxVal = (double) 0x7fff; | |||||
| char* intData = static_cast<char*> (dest); | |||||
| if (dest != (void*) source || destBytesPerSample <= 4) | |||||
| { | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| *(uint16*) intData = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| intData += destBytesPerSample; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| intData += destBytesPerSample * numSamples; | |||||
| for (int i = numSamples; --i >= 0;) | |||||
| { | |||||
| intData -= destBytesPerSample; | |||||
| *(uint16*) intData = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertFloatToInt16BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) | |||||
| { | |||||
| const double maxVal = (double) 0x7fff; | |||||
| char* intData = static_cast<char*> (dest); | |||||
| if (dest != (void*) source || destBytesPerSample <= 4) | |||||
| { | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| *(uint16*) intData = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| intData += destBytesPerSample; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| intData += destBytesPerSample * numSamples; | |||||
| for (int i = numSamples; --i >= 0;) | |||||
| { | |||||
| intData -= destBytesPerSample; | |||||
| *(uint16*) intData = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertFloatToInt24LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) | |||||
| { | |||||
| const double maxVal = (double) 0x7fffff; | |||||
| char* intData = static_cast<char*> (dest); | |||||
| if (dest != (void*) source || destBytesPerSample <= 4) | |||||
| { | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| ByteOrder::littleEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); | |||||
| intData += destBytesPerSample; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| intData += destBytesPerSample * numSamples; | |||||
| for (int i = numSamples; --i >= 0;) | |||||
| { | |||||
| intData -= destBytesPerSample; | |||||
| ByteOrder::littleEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertFloatToInt24BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) | |||||
| { | |||||
| const double maxVal = (double) 0x7fffff; | |||||
| char* intData = static_cast<char*> (dest); | |||||
| if (dest != (void*) source || destBytesPerSample <= 4) | |||||
| { | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| ByteOrder::bigEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); | |||||
| intData += destBytesPerSample; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| intData += destBytesPerSample * numSamples; | |||||
| for (int i = numSamples; --i >= 0;) | |||||
| { | |||||
| intData -= destBytesPerSample; | |||||
| ByteOrder::bigEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertFloatToInt32LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) | |||||
| { | |||||
| const double maxVal = (double) 0x7fffffff; | |||||
| char* intData = static_cast<char*> (dest); | |||||
| if (dest != (void*) source || destBytesPerSample <= 4) | |||||
| { | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| *(uint32*)intData = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| intData += destBytesPerSample; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| intData += destBytesPerSample * numSamples; | |||||
| for (int i = numSamples; --i >= 0;) | |||||
| { | |||||
| intData -= destBytesPerSample; | |||||
| *(uint32*)intData = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertFloatToInt32BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) | |||||
| { | |||||
| const double maxVal = (double) 0x7fffffff; | |||||
| char* intData = static_cast<char*> (dest); | |||||
| if (dest != (void*) source || destBytesPerSample <= 4) | |||||
| { | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| *(uint32*)intData = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| intData += destBytesPerSample; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| intData += destBytesPerSample * numSamples; | |||||
| for (int i = numSamples; --i >= 0;) | |||||
| { | |||||
| intData -= destBytesPerSample; | |||||
| *(uint32*)intData = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertFloatToFloat32LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) | |||||
| { | |||||
| jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data! | |||||
| char* d = static_cast<char*> (dest); | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| *(float*) d = source[i]; | |||||
| #if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ | |||||
| *(uint32*) d = ByteOrder::swap (*(uint32*) d); | |||||
| #endif | |||||
| d += destBytesPerSample; | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertFloatToFloat32BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) | |||||
| { | |||||
| jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data! | |||||
| char* d = static_cast<char*> (dest); | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| *(float*) d = source[i]; | |||||
| #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ | |||||
| *(uint32*) d = ByteOrder::swap (*(uint32*) d); | |||||
| #endif | |||||
| d += destBytesPerSample; | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| void AudioDataConverters::convertInt16LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) | |||||
| { | |||||
| const float scale = 1.0f / 0x7fff; | |||||
| const char* intData = static_cast<const char*> (source); | |||||
| if (source != (void*) dest || srcBytesPerSample >= 4) | |||||
| { | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*(uint16*)intData); | |||||
| intData += srcBytesPerSample; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| intData += srcBytesPerSample * numSamples; | |||||
| for (int i = numSamples; --i >= 0;) | |||||
| { | |||||
| intData -= srcBytesPerSample; | |||||
| dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*(uint16*)intData); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertInt16BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) | |||||
| { | |||||
| const float scale = 1.0f / 0x7fff; | |||||
| const char* intData = static_cast<const char*> (source); | |||||
| if (source != (void*) dest || srcBytesPerSample >= 4) | |||||
| { | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*(uint16*)intData); | |||||
| intData += srcBytesPerSample; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| intData += srcBytesPerSample * numSamples; | |||||
| for (int i = numSamples; --i >= 0;) | |||||
| { | |||||
| intData -= srcBytesPerSample; | |||||
| dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*(uint16*)intData); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertInt24LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) | |||||
| { | |||||
| const float scale = 1.0f / 0x7fffff; | |||||
| const char* intData = static_cast<const char*> (source); | |||||
| if (source != (void*) dest || srcBytesPerSample >= 4) | |||||
| { | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| dest[i] = scale * (short) ByteOrder::littleEndian24Bit (intData); | |||||
| intData += srcBytesPerSample; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| intData += srcBytesPerSample * numSamples; | |||||
| for (int i = numSamples; --i >= 0;) | |||||
| { | |||||
| intData -= srcBytesPerSample; | |||||
| dest[i] = scale * (short) ByteOrder::littleEndian24Bit (intData); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertInt24BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) | |||||
| { | |||||
| const float scale = 1.0f / 0x7fffff; | |||||
| const char* intData = static_cast<const char*> (source); | |||||
| if (source != (void*) dest || srcBytesPerSample >= 4) | |||||
| { | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| dest[i] = scale * (short) ByteOrder::bigEndian24Bit (intData); | |||||
| intData += srcBytesPerSample; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| intData += srcBytesPerSample * numSamples; | |||||
| for (int i = numSamples; --i >= 0;) | |||||
| { | |||||
| intData -= srcBytesPerSample; | |||||
| dest[i] = scale * (short) ByteOrder::bigEndian24Bit (intData); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertInt32LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) | |||||
| { | |||||
| const float scale = 1.0f / 0x7fffffff; | |||||
| const char* intData = static_cast<const char*> (source); | |||||
| if (source != (void*) dest || srcBytesPerSample >= 4) | |||||
| { | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| dest[i] = scale * (int) ByteOrder::swapIfBigEndian (*(uint32*) intData); | |||||
| intData += srcBytesPerSample; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| intData += srcBytesPerSample * numSamples; | |||||
| for (int i = numSamples; --i >= 0;) | |||||
| { | |||||
| intData -= srcBytesPerSample; | |||||
| dest[i] = scale * (int) ByteOrder::swapIfBigEndian (*(uint32*) intData); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertInt32BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) | |||||
| { | |||||
| const float scale = 1.0f / 0x7fffffff; | |||||
| const char* intData = static_cast<const char*> (source); | |||||
| if (source != (void*) dest || srcBytesPerSample >= 4) | |||||
| { | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| dest[i] = scale * (int) ByteOrder::swapIfLittleEndian (*(uint32*) intData); | |||||
| intData += srcBytesPerSample; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| intData += srcBytesPerSample * numSamples; | |||||
| for (int i = numSamples; --i >= 0;) | |||||
| { | |||||
| intData -= srcBytesPerSample; | |||||
| dest[i] = scale * (int) ByteOrder::swapIfLittleEndian (*(uint32*) intData); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertFloat32LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) | |||||
| { | |||||
| const char* s = static_cast<const char*> (source); | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| dest[i] = *(float*)s; | |||||
| #if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ | |||||
| uint32* const d = (uint32*) (dest + i); | |||||
| *d = ByteOrder::swap (*d); | |||||
| #endif | |||||
| s += srcBytesPerSample; | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertFloat32BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) | |||||
| { | |||||
| const char* s = static_cast<const char*> (source); | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| dest[i] = *(float*)s; | |||||
| #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ | |||||
| uint32* const d = (uint32*) (dest + i); | |||||
| *d = ByteOrder::swap (*d); | |||||
| #endif | |||||
| s += srcBytesPerSample; | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| void AudioDataConverters::convertFloatToFormat (const DataFormat destFormat, | |||||
| const float* const source, | |||||
| void* const dest, | |||||
| const int numSamples) | |||||
| { | |||||
| switch (destFormat) | |||||
| { | |||||
| case int16LE: convertFloatToInt16LE (source, dest, numSamples); break; | |||||
| case int16BE: convertFloatToInt16BE (source, dest, numSamples); break; | |||||
| case int24LE: convertFloatToInt24LE (source, dest, numSamples); break; | |||||
| case int24BE: convertFloatToInt24BE (source, dest, numSamples); break; | |||||
| case int32LE: convertFloatToInt32LE (source, dest, numSamples); break; | |||||
| case int32BE: convertFloatToInt32BE (source, dest, numSamples); break; | |||||
| case float32LE: convertFloatToFloat32LE (source, dest, numSamples); break; | |||||
| case float32BE: convertFloatToFloat32BE (source, dest, numSamples); break; | |||||
| default: jassertfalse; break; | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertFormatToFloat (const DataFormat sourceFormat, | |||||
| const void* const source, | |||||
| float* const dest, | |||||
| const int numSamples) | |||||
| { | |||||
| switch (sourceFormat) | |||||
| { | |||||
| case int16LE: convertInt16LEToFloat (source, dest, numSamples); break; | |||||
| case int16BE: convertInt16BEToFloat (source, dest, numSamples); break; | |||||
| case int24LE: convertInt24LEToFloat (source, dest, numSamples); break; | |||||
| case int24BE: convertInt24BEToFloat (source, dest, numSamples); break; | |||||
| case int32LE: convertInt32LEToFloat (source, dest, numSamples); break; | |||||
| case int32BE: convertInt32BEToFloat (source, dest, numSamples); break; | |||||
| case float32LE: convertFloat32LEToFloat (source, dest, numSamples); break; | |||||
| case float32BE: convertFloat32BEToFloat (source, dest, numSamples); break; | |||||
| default: jassertfalse; break; | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| void AudioDataConverters::interleaveSamples (const float** const source, | |||||
| float* const dest, | |||||
| const int numSamples, | |||||
| const int numChannels) | |||||
| { | |||||
| for (int chan = 0; chan < numChannels; ++chan) | |||||
| { | |||||
| int i = chan; | |||||
| const float* src = source [chan]; | |||||
| for (int j = 0; j < numSamples; ++j) | |||||
| { | |||||
| dest [i] = src [j]; | |||||
| i += numChannels; | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::deinterleaveSamples (const float* const source, | |||||
| float** const dest, | |||||
| const int numSamples, | |||||
| const int numChannels) | |||||
| { | |||||
| for (int chan = 0; chan < numChannels; ++chan) | |||||
| { | |||||
| int i = chan; | |||||
| float* dst = dest [chan]; | |||||
| for (int j = 0; j < numSamples; ++j) | |||||
| { | |||||
| dst [j] = source [i]; | |||||
| i += numChannels; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,719 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the Water library. | |||||
| Copyright (c) 2016 ROLI Ltd. | |||||
| Copyright (C) 2018 Filipe Coelho <falktx@falktx.com> | |||||
| Permission is granted to use this software under the terms of the ISC license | |||||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
| Permission to use, copy, modify, and/or distribute this software for any | |||||
| purpose with or without fee is hereby granted, provided that the above | |||||
| copyright notice and this permission notice appear in all copies. | |||||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
| OF THIS SOFTWARE. | |||||
| ============================================================================== | |||||
| */ | |||||
| #ifndef WATER_AUDIODATACONVERTERS_H_INCLUDED | |||||
| #define WATER_AUDIODATACONVERTERS_H_INCLUDED | |||||
| #include "../memory/ByteOrder.h" | |||||
| namespace water { | |||||
| //============================================================================== | |||||
| /** | |||||
| This class a container which holds all the classes pertaining to the AudioData::Pointer | |||||
| audio sample format class. | |||||
| @see AudioData::Pointer. | |||||
| */ | |||||
| class AudioData | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| // These types can be used as the SampleFormat template parameter for the AudioData::Pointer class. | |||||
| class Int8; /**< Used as a template parameter for AudioData::Pointer. Indicates an 8-bit integer packed data format. */ | |||||
| class UInt8; /**< Used as a template parameter for AudioData::Pointer. Indicates an 8-bit unsigned integer packed data format. */ | |||||
| class Int16; /**< Used as a template parameter for AudioData::Pointer. Indicates an 16-bit integer packed data format. */ | |||||
| class Int24; /**< Used as a template parameter for AudioData::Pointer. Indicates an 24-bit integer packed data format. */ | |||||
| class Int32; /**< Used as a template parameter for AudioData::Pointer. Indicates an 32-bit integer packed data format. */ | |||||
| class Float32; /**< Used as a template parameter for AudioData::Pointer. Indicates an 32-bit float data format. */ | |||||
| //============================================================================== | |||||
| // These types can be used as the Endianness template parameter for the AudioData::Pointer class. | |||||
| class BigEndian; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored in big-endian order. */ | |||||
| class LittleEndian; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored in little-endian order. */ | |||||
| class NativeEndian; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored in the CPU's native endianness. */ | |||||
| //============================================================================== | |||||
| // These types can be used as the InterleavingType template parameter for the AudioData::Pointer class. | |||||
| class NonInterleaved; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored contiguously. */ | |||||
| class Interleaved; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are interleaved with a number of other channels. */ | |||||
| //============================================================================== | |||||
| // These types can be used as the Constness template parameter for the AudioData::Pointer class. | |||||
| class NonConst; /**< Used as a template parameter for AudioData::Pointer. Indicates that the pointer can be used for non-const data. */ | |||||
| class Const; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples can only be used for const data.. */ | |||||
| #ifndef DOXYGEN | |||||
| //============================================================================== | |||||
| class BigEndian | |||||
| { | |||||
| public: | |||||
| template <class SampleFormatType> static inline float getAsFloat (SampleFormatType& s) noexcept { return s.getAsFloatBE(); } | |||||
| template <class SampleFormatType> static inline void setAsFloat (SampleFormatType& s, float newValue) noexcept { s.setAsFloatBE (newValue); } | |||||
| template <class SampleFormatType> static inline int32 getAsInt32 (SampleFormatType& s) noexcept { return s.getAsInt32BE(); } | |||||
| template <class SampleFormatType> static inline void setAsInt32 (SampleFormatType& s, int32 newValue) noexcept { s.setAsInt32BE (newValue); } | |||||
| template <class SourceType, class DestType> static inline void copyFrom (DestType& dest, SourceType& source) noexcept { dest.copyFromBE (source); } | |||||
| enum { isBigEndian = 1 }; | |||||
| }; | |||||
| class LittleEndian | |||||
| { | |||||
| public: | |||||
| template <class SampleFormatType> static inline float getAsFloat (SampleFormatType& s) noexcept { return s.getAsFloatLE(); } | |||||
| template <class SampleFormatType> static inline void setAsFloat (SampleFormatType& s, float newValue) noexcept { s.setAsFloatLE (newValue); } | |||||
| template <class SampleFormatType> static inline int32 getAsInt32 (SampleFormatType& s) noexcept { return s.getAsInt32LE(); } | |||||
| template <class SampleFormatType> static inline void setAsInt32 (SampleFormatType& s, int32 newValue) noexcept { s.setAsInt32LE (newValue); } | |||||
| template <class SourceType, class DestType> static inline void copyFrom (DestType& dest, SourceType& source) noexcept { dest.copyFromLE (source); } | |||||
| enum { isBigEndian = 0 }; | |||||
| }; | |||||
| #if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ | |||||
| class NativeEndian : public BigEndian {}; | |||||
| #else | |||||
| class NativeEndian : public LittleEndian {}; | |||||
| #endif | |||||
| //============================================================================== | |||||
| class Int8 | |||||
| { | |||||
| public: | |||||
| inline Int8 (void* d) noexcept : data (static_cast<int8*> (d)) {} | |||||
| inline void advance() noexcept { ++data; } | |||||
| inline void skip (int numSamples) noexcept { data += numSamples; } | |||||
| inline float getAsFloatLE() const noexcept { return (float) (*data * (1.0 / (1.0 + maxValue))); } | |||||
| inline float getAsFloatBE() const noexcept { return getAsFloatLE(); } | |||||
| inline void setAsFloatLE (float newValue) noexcept { *data = (int8) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + maxValue))); } | |||||
| inline void setAsFloatBE (float newValue) noexcept { setAsFloatLE (newValue); } | |||||
| inline int32 getAsInt32LE() const noexcept { return (int) (*data << 24); } | |||||
| inline int32 getAsInt32BE() const noexcept { return getAsInt32LE(); } | |||||
| inline void setAsInt32LE (int newValue) noexcept { *data = (int8) (newValue >> 24); } | |||||
| inline void setAsInt32BE (int newValue) noexcept { setAsInt32LE (newValue); } | |||||
| inline void clear() noexcept { *data = 0; } | |||||
| inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} | |||||
| template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } | |||||
| template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } | |||||
| inline void copyFromSameType (Int8& source) noexcept { *data = *source.data; } | |||||
| int8* data; | |||||
| enum { bytesPerSample = 1, maxValue = 0x7f, resolution = (1 << 24), isFloat = 0 }; | |||||
| }; | |||||
| class UInt8 | |||||
| { | |||||
| public: | |||||
| inline UInt8 (void* d) noexcept : data (static_cast<uint8*> (d)) {} | |||||
| inline void advance() noexcept { ++data; } | |||||
| inline void skip (int numSamples) noexcept { data += numSamples; } | |||||
| inline float getAsFloatLE() const noexcept { return (float) ((*data - 128) * (1.0 / (1.0 + maxValue))); } | |||||
| inline float getAsFloatBE() const noexcept { return getAsFloatLE(); } | |||||
| inline void setAsFloatLE (float newValue) noexcept { *data = (uint8) jlimit (0, 255, 128 + roundToInt (newValue * (1.0 + maxValue))); } | |||||
| inline void setAsFloatBE (float newValue) noexcept { setAsFloatLE (newValue); } | |||||
| inline int32 getAsInt32LE() const noexcept { return (int) ((*data - 128) << 24); } | |||||
| inline int32 getAsInt32BE() const noexcept { return getAsInt32LE(); } | |||||
| inline void setAsInt32LE (int newValue) noexcept { *data = (uint8) (128 + (newValue >> 24)); } | |||||
| inline void setAsInt32BE (int newValue) noexcept { setAsInt32LE (newValue); } | |||||
| inline void clear() noexcept { *data = 128; } | |||||
| inline void clearMultiple (int num) noexcept { memset (data, 128, (size_t) num) ;} | |||||
| template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } | |||||
| template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } | |||||
| inline void copyFromSameType (UInt8& source) noexcept { *data = *source.data; } | |||||
| uint8* data; | |||||
| enum { bytesPerSample = 1, maxValue = 0x7f, resolution = (1 << 24), isFloat = 0 }; | |||||
| }; | |||||
| class Int16 | |||||
| { | |||||
| public: | |||||
| inline Int16 (void* d) noexcept : data (static_cast<uint16*> (d)) {} | |||||
| inline void advance() noexcept { ++data; } | |||||
| inline void skip (int numSamples) noexcept { data += numSamples; } | |||||
| inline float getAsFloatLE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int16) ByteOrder::swapIfBigEndian (*data)); } | |||||
| inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int16) ByteOrder::swapIfLittleEndian (*data)); } | |||||
| inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint16) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + maxValue)))); } | |||||
| inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint16) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + maxValue)))); } | |||||
| inline int32 getAsInt32LE() const noexcept { return (int32) (ByteOrder::swapIfBigEndian ((uint16) *data) << 16); } | |||||
| inline int32 getAsInt32BE() const noexcept { return (int32) (ByteOrder::swapIfLittleEndian ((uint16) *data) << 16); } | |||||
| inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint16) (newValue >> 16)); } | |||||
| inline void setAsInt32BE (int32 newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint16) (newValue >> 16)); } | |||||
| inline void clear() noexcept { *data = 0; } | |||||
| inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} | |||||
| template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } | |||||
| template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } | |||||
| inline void copyFromSameType (Int16& source) noexcept { *data = *source.data; } | |||||
| uint16* data; | |||||
| enum { bytesPerSample = 2, maxValue = 0x7fff, resolution = (1 << 16), isFloat = 0 }; | |||||
| }; | |||||
| class Int24 | |||||
| { | |||||
| public: | |||||
| inline Int24 (void* d) noexcept : data (static_cast<char*> (d)) {} | |||||
| inline void advance() noexcept { data += 3; } | |||||
| inline void skip (int numSamples) noexcept { data += 3 * numSamples; } | |||||
| inline float getAsFloatLE() const noexcept { return (float) (ByteOrder::littleEndian24Bit (data) * (1.0 / (1.0 + maxValue))); } | |||||
| inline float getAsFloatBE() const noexcept { return (float) (ByteOrder::bigEndian24Bit (data) * (1.0 / (1.0 + maxValue))); } | |||||
| inline void setAsFloatLE (float newValue) noexcept { ByteOrder::littleEndian24BitToChars (jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + maxValue))), data); } | |||||
| inline void setAsFloatBE (float newValue) noexcept { ByteOrder::bigEndian24BitToChars (jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + maxValue))), data); } | |||||
| inline int32 getAsInt32LE() const noexcept { return (int32) ByteOrder::littleEndian24Bit (data) << 8; } | |||||
| inline int32 getAsInt32BE() const noexcept { return (int32) ByteOrder::bigEndian24Bit (data) << 8; } | |||||
| inline void setAsInt32LE (int32 newValue) noexcept { ByteOrder::littleEndian24BitToChars (newValue >> 8, data); } | |||||
| inline void setAsInt32BE (int32 newValue) noexcept { ByteOrder::bigEndian24BitToChars (newValue >> 8, data); } | |||||
| inline void clear() noexcept { data[0] = 0; data[1] = 0; data[2] = 0; } | |||||
| inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} | |||||
| template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } | |||||
| template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } | |||||
| inline void copyFromSameType (Int24& source) noexcept { data[0] = source.data[0]; data[1] = source.data[1]; data[2] = source.data[2]; } | |||||
| char* data; | |||||
| enum { bytesPerSample = 3, maxValue = 0x7fffff, resolution = (1 << 8), isFloat = 0 }; | |||||
| }; | |||||
| class Int32 | |||||
| { | |||||
| public: | |||||
| inline Int32 (void* d) noexcept : data (static_cast<uint32*> (d)) {} | |||||
| inline void advance() noexcept { ++data; } | |||||
| inline void skip (int numSamples) noexcept { data += numSamples; } | |||||
| inline float getAsFloatLE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int32) ByteOrder::swapIfBigEndian (*data)); } | |||||
| inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int32) ByteOrder::swapIfLittleEndian (*data)); } | |||||
| inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) (maxValue * jlimit (-1.0, 1.0, (double) newValue))); } | |||||
| inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) (maxValue * jlimit (-1.0, 1.0, (double) newValue))); } | |||||
| inline int32 getAsInt32LE() const noexcept { return (int32) ByteOrder::swapIfBigEndian (*data); } | |||||
| inline int32 getAsInt32BE() const noexcept { return (int32) ByteOrder::swapIfLittleEndian (*data); } | |||||
| inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) newValue); } | |||||
| inline void setAsInt32BE (int32 newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) newValue); } | |||||
| inline void clear() noexcept { *data = 0; } | |||||
| inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} | |||||
| template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } | |||||
| template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } | |||||
| inline void copyFromSameType (Int32& source) noexcept { *data = *source.data; } | |||||
| uint32* data; | |||||
| enum { bytesPerSample = 4, maxValue = 0x7fffffff, resolution = 1, isFloat = 0 }; | |||||
| }; | |||||
| /** A 32-bit integer type, of which only the bottom 24 bits are used. */ | |||||
| class Int24in32 : public Int32 | |||||
| { | |||||
| public: | |||||
| inline Int24in32 (void* d) noexcept : Int32 (d) {} | |||||
| inline float getAsFloatLE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int32) ByteOrder::swapIfBigEndian (*data)); } | |||||
| inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int32) ByteOrder::swapIfLittleEndian (*data)); } | |||||
| inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) (maxValue * jlimit (-1.0, 1.0, (double) newValue))); } | |||||
| inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) (maxValue * jlimit (-1.0, 1.0, (double) newValue))); } | |||||
| inline int32 getAsInt32LE() const noexcept { return (int32) ByteOrder::swapIfBigEndian (*data) << 8; } | |||||
| inline int32 getAsInt32BE() const noexcept { return (int32) ByteOrder::swapIfLittleEndian (*data) << 8; } | |||||
| inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) newValue >> 8); } | |||||
| inline void setAsInt32BE (int32 newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) newValue >> 8); } | |||||
| template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } | |||||
| template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } | |||||
| inline void copyFromSameType (Int24in32& source) noexcept { *data = *source.data; } | |||||
| enum { bytesPerSample = 4, maxValue = 0x7fffff, resolution = (1 << 8), isFloat = 0 }; | |||||
| }; | |||||
| class Float32 | |||||
| { | |||||
| public: | |||||
| inline Float32 (void* d) noexcept : data (static_cast<float*> (d)) {} | |||||
| inline void advance() noexcept { ++data; } | |||||
| inline void skip (int numSamples) noexcept { data += numSamples; } | |||||
| #if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ | |||||
| inline float getAsFloatBE() const noexcept { return *data; } | |||||
| inline void setAsFloatBE (float newValue) noexcept { *data = newValue; } | |||||
| inline float getAsFloatLE() const noexcept { union { uint32 asInt; float asFloat; } n; n.asInt = ByteOrder::swap (*(uint32*) data); return n.asFloat; } | |||||
| inline void setAsFloatLE (float newValue) noexcept { union { uint32 asInt; float asFloat; } n; n.asFloat = newValue; *(uint32*) data = ByteOrder::swap (n.asInt); } | |||||
| #else | |||||
| inline float getAsFloatLE() const noexcept { return *data; } | |||||
| inline void setAsFloatLE (float newValue) noexcept { *data = newValue; } | |||||
| inline float getAsFloatBE() const noexcept { union { uint32 asInt; float asFloat; } n; n.asInt = ByteOrder::swap (*(uint32*) data); return n.asFloat; } | |||||
| inline void setAsFloatBE (float newValue) noexcept { union { uint32 asInt; float asFloat; } n; n.asFloat = newValue; *(uint32*) data = ByteOrder::swap (n.asInt); } | |||||
| #endif | |||||
| inline int32 getAsInt32LE() const noexcept { return (int32) roundToInt (jlimit (-1.0, 1.0, (double) getAsFloatLE()) * (double) maxValue); } | |||||
| inline int32 getAsInt32BE() const noexcept { return (int32) roundToInt (jlimit (-1.0, 1.0, (double) getAsFloatBE()) * (double) maxValue); } | |||||
| inline void setAsInt32LE (int32 newValue) noexcept { setAsFloatLE ((float) (newValue * (1.0 / (1.0 + maxValue)))); } | |||||
| inline void setAsInt32BE (int32 newValue) noexcept { setAsFloatBE ((float) (newValue * (1.0 / (1.0 + maxValue)))); } | |||||
| inline void clear() noexcept { *data = 0; } | |||||
| inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} | |||||
| template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsFloatLE (source.getAsFloat()); } | |||||
| template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsFloatBE (source.getAsFloat()); } | |||||
| inline void copyFromSameType (Float32& source) noexcept { *data = *source.data; } | |||||
| float* data; | |||||
| enum { bytesPerSample = 4, maxValue = 0x7fffffff, resolution = (1 << 8), isFloat = 1 }; | |||||
| }; | |||||
| //============================================================================== | |||||
| class NonInterleaved | |||||
| { | |||||
| public: | |||||
| inline NonInterleaved() noexcept {} | |||||
| inline NonInterleaved (const NonInterleaved&) noexcept {} | |||||
| inline NonInterleaved (const int) noexcept {} | |||||
| inline void copyFrom (const NonInterleaved&) noexcept {} | |||||
| template <class SampleFormatType> inline void advanceData (SampleFormatType& s) noexcept { s.advance(); } | |||||
| template <class SampleFormatType> inline void advanceDataBy (SampleFormatType& s, int numSamples) noexcept { s.skip (numSamples); } | |||||
| template <class SampleFormatType> inline void clear (SampleFormatType& s, int numSamples) noexcept { s.clearMultiple (numSamples); } | |||||
| template <class SampleFormatType> inline static int getNumBytesBetweenSamples (const SampleFormatType&) noexcept { return SampleFormatType::bytesPerSample; } | |||||
| enum { isInterleavedType = 0, numInterleavedChannels = 1 }; | |||||
| }; | |||||
| class Interleaved | |||||
| { | |||||
| public: | |||||
| inline Interleaved() noexcept : numInterleavedChannels (1) {} | |||||
| inline Interleaved (const Interleaved& other) noexcept : numInterleavedChannels (other.numInterleavedChannels) {} | |||||
| inline Interleaved (const int numInterleavedChans) noexcept : numInterleavedChannels (numInterleavedChans) {} | |||||
| inline void copyFrom (const Interleaved& other) noexcept { numInterleavedChannels = other.numInterleavedChannels; } | |||||
| template <class SampleFormatType> inline void advanceData (SampleFormatType& s) noexcept { s.skip (numInterleavedChannels); } | |||||
| template <class SampleFormatType> inline void advanceDataBy (SampleFormatType& s, int numSamples) noexcept { s.skip (numInterleavedChannels * numSamples); } | |||||
| template <class SampleFormatType> inline void clear (SampleFormatType& s, int numSamples) noexcept { while (--numSamples >= 0) { s.clear(); s.skip (numInterleavedChannels); } } | |||||
| template <class SampleFormatType> inline int getNumBytesBetweenSamples (const SampleFormatType&) const noexcept { return numInterleavedChannels * SampleFormatType::bytesPerSample; } | |||||
| int numInterleavedChannels; | |||||
| enum { isInterleavedType = 1 }; | |||||
| }; | |||||
| //============================================================================== | |||||
| class NonConst | |||||
| { | |||||
| public: | |||||
| typedef void VoidType; | |||||
| static inline void* toVoidPtr (VoidType* v) noexcept { return v; } | |||||
| enum { isConst = 0 }; | |||||
| }; | |||||
| class Const | |||||
| { | |||||
| public: | |||||
| typedef const void VoidType; | |||||
| static inline void* toVoidPtr (VoidType* v) noexcept { return const_cast<void*> (v); } | |||||
| enum { isConst = 1 }; | |||||
| }; | |||||
| #endif | |||||
| //============================================================================== | |||||
| /** | |||||
| A pointer to a block of audio data with a particular encoding. | |||||
| This object can be used to read and write from blocks of encoded audio samples. To create one, you specify | |||||
| the audio format as a series of template parameters, e.g. | |||||
| @code | |||||
| // this creates a pointer for reading from a const array of 16-bit little-endian packed samples. | |||||
| AudioData::Pointer <AudioData::Int16, | |||||
| AudioData::LittleEndian, | |||||
| AudioData::NonInterleaved, | |||||
| AudioData::Const> pointer (someRawAudioData); | |||||
| // These methods read the sample that is being pointed to | |||||
| float firstSampleAsFloat = pointer.getAsFloat(); | |||||
| int32 firstSampleAsInt = pointer.getAsInt32(); | |||||
| ++pointer; // moves the pointer to the next sample. | |||||
| pointer += 3; // skips the next 3 samples. | |||||
| @endcode | |||||
| The convertSamples() method lets you copy a range of samples from one format to another, automatically | |||||
| converting its format. | |||||
| @see AudioData::Converter | |||||
| */ | |||||
| template <typename SampleFormat, | |||||
| typename Endianness, | |||||
| typename InterleavingType, | |||||
| typename Constness> | |||||
| class Pointer : private InterleavingType // (inherited for EBCO) | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Creates a non-interleaved pointer from some raw data in the appropriate format. | |||||
| This constructor is only used if you've specified the AudioData::NonInterleaved option - | |||||
| for interleaved formats, use the constructor that also takes a number of channels. | |||||
| */ | |||||
| Pointer (typename Constness::VoidType* sourceData) noexcept | |||||
| : data (Constness::toVoidPtr (sourceData)) | |||||
| { | |||||
| // If you're using interleaved data, call the other constructor! If you're using non-interleaved data, | |||||
| // you should pass NonInterleaved as the template parameter for the interleaving type! | |||||
| static_jassert (InterleavingType::isInterleavedType == 0); | |||||
| } | |||||
| /** Creates a pointer from some raw data in the appropriate format with the specified number of interleaved channels. | |||||
| For non-interleaved data, use the other constructor. | |||||
| */ | |||||
| Pointer (typename Constness::VoidType* sourceData, int numInterleaved) noexcept | |||||
| : InterleavingType (numInterleaved), data (Constness::toVoidPtr (sourceData)) | |||||
| { | |||||
| } | |||||
| /** Creates a copy of another pointer. */ | |||||
| Pointer (const Pointer& other) noexcept | |||||
| : InterleavingType (other), data (other.data) | |||||
| { | |||||
| } | |||||
| Pointer& operator= (const Pointer& other) noexcept | |||||
| { | |||||
| InterleavingType::operator= (other); | |||||
| data = other.data; | |||||
| return *this; | |||||
| } | |||||
| //============================================================================== | |||||
| /** Returns the value of the first sample as a floating point value. | |||||
| The value will be in the range -1.0 to 1.0 for integer formats. For floating point | |||||
| formats, the value could be outside that range, although -1 to 1 is the standard range. | |||||
| */ | |||||
| inline float getAsFloat() const noexcept { return Endianness::getAsFloat (data); } | |||||
| /** Sets the value of the first sample as a floating point value. | |||||
| (This method can only be used if the AudioData::NonConst option was used). | |||||
| The value should be in the range -1.0 to 1.0 - for integer formats, values outside that | |||||
| range will be clipped. For floating point formats, any value passed in here will be | |||||
| written directly, although -1 to 1 is the standard range. | |||||
| */ | |||||
| inline void setAsFloat (float newValue) noexcept | |||||
| { | |||||
| static_jassert (Constness::isConst == 0); // trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead! | |||||
| Endianness::setAsFloat (data, newValue); | |||||
| } | |||||
| /** Returns the value of the first sample as a 32-bit integer. | |||||
| The value returned will be in the range 0x80000000 to 0x7fffffff, and shorter values will be | |||||
| shifted to fill this range (e.g. if you're reading from 24-bit data, the values will be shifted up | |||||
| by 8 bits when returned here). If the source data is floating point, values beyond -1.0 to 1.0 will | |||||
| be clipped so that -1.0 maps onto -0x7fffffff and 1.0 maps to 0x7fffffff. | |||||
| */ | |||||
| inline int32 getAsInt32() const noexcept { return Endianness::getAsInt32 (data); } | |||||
| /** Sets the value of the first sample as a 32-bit integer. | |||||
| This will be mapped to the range of the format that is being written - see getAsInt32(). | |||||
| */ | |||||
| inline void setAsInt32 (int32 newValue) noexcept | |||||
| { | |||||
| static_jassert (Constness::isConst == 0); // trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead! | |||||
| Endianness::setAsInt32 (data, newValue); | |||||
| } | |||||
| /** Moves the pointer along to the next sample. */ | |||||
| inline Pointer& operator++() noexcept { advance(); return *this; } | |||||
| /** Moves the pointer back to the previous sample. */ | |||||
| inline Pointer& operator--() noexcept { this->advanceDataBy (data, -1); return *this; } | |||||
| /** Adds a number of samples to the pointer's position. */ | |||||
| Pointer& operator+= (int samplesToJump) noexcept { this->advanceDataBy (data, samplesToJump); return *this; } | |||||
| /** Writes a stream of samples into this pointer from another pointer. | |||||
| This will copy the specified number of samples, converting between formats appropriately. | |||||
| */ | |||||
| void convertSamples (Pointer source, int numSamples) const noexcept | |||||
| { | |||||
| static_jassert (Constness::isConst == 0); // trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead! | |||||
| for (Pointer dest (*this); --numSamples >= 0;) | |||||
| { | |||||
| dest.data.copyFromSameType (source.data); | |||||
| dest.advance(); | |||||
| source.advance(); | |||||
| } | |||||
| } | |||||
| /** Writes a stream of samples into this pointer from another pointer. | |||||
| This will copy the specified number of samples, converting between formats appropriately. | |||||
| */ | |||||
| template <class OtherPointerType> | |||||
| void convertSamples (OtherPointerType source, int numSamples) const noexcept | |||||
| { | |||||
| static_jassert (Constness::isConst == 0); // trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead! | |||||
| Pointer dest (*this); | |||||
| if (source.getRawData() != getRawData() || source.getNumBytesBetweenSamples() >= getNumBytesBetweenSamples()) | |||||
| { | |||||
| while (--numSamples >= 0) | |||||
| { | |||||
| Endianness::copyFrom (dest.data, source); | |||||
| dest.advance(); | |||||
| ++source; | |||||
| } | |||||
| } | |||||
| else // copy backwards if we're increasing the sample width.. | |||||
| { | |||||
| dest += numSamples; | |||||
| source += numSamples; | |||||
| while (--numSamples >= 0) | |||||
| Endianness::copyFrom ((--dest).data, --source); | |||||
| } | |||||
| } | |||||
| /** Sets a number of samples to zero. */ | |||||
| void clearSamples (int numSamples) const noexcept | |||||
| { | |||||
| Pointer dest (*this); | |||||
| dest.clear (dest.data, numSamples); | |||||
| } | |||||
| #if 0 | |||||
| /** Scans a block of data, returning the lowest and highest levels as floats */ | |||||
| Range<float> findMinAndMax (size_t numSamples) const noexcept | |||||
| { | |||||
| if (numSamples == 0) | |||||
| return Range<float>(); | |||||
| Pointer dest (*this); | |||||
| if (isFloatingPoint()) | |||||
| { | |||||
| float mn = dest.getAsFloat(); | |||||
| dest.advance(); | |||||
| float mx = mn; | |||||
| while (--numSamples > 0) | |||||
| { | |||||
| const float v = dest.getAsFloat(); | |||||
| dest.advance(); | |||||
| if (mx < v) mx = v; | |||||
| if (v < mn) mn = v; | |||||
| } | |||||
| return Range<float> (mn, mx); | |||||
| } | |||||
| int32 mn = dest.getAsInt32(); | |||||
| dest.advance(); | |||||
| int32 mx = mn; | |||||
| while (--numSamples > 0) | |||||
| { | |||||
| const int v = dest.getAsInt32(); | |||||
| dest.advance(); | |||||
| if (mx < v) mx = v; | |||||
| if (v < mn) mn = v; | |||||
| } | |||||
| return Range<float> (mn * (float) (1.0 / (1.0 + Int32::maxValue)), | |||||
| mx * (float) (1.0 / (1.0 + Int32::maxValue))); | |||||
| } | |||||
| /** Scans a block of data, returning the lowest and highest levels as floats */ | |||||
| void findMinAndMax (size_t numSamples, float& minValue, float& maxValue) const noexcept | |||||
| { | |||||
| Range<float> r (findMinAndMax (numSamples)); | |||||
| minValue = r.getStart(); | |||||
| maxValue = r.getEnd(); | |||||
| } | |||||
| #endif | |||||
| /** Returns true if the pointer is using a floating-point format. */ | |||||
| static bool isFloatingPoint() noexcept { return (bool) SampleFormat::isFloat; } | |||||
| /** Returns true if the format is big-endian. */ | |||||
| static bool isBigEndian() noexcept { return (bool) Endianness::isBigEndian; } | |||||
| /** Returns the number of bytes in each sample (ignoring the number of interleaved channels). */ | |||||
| static int getBytesPerSample() noexcept { return (int) SampleFormat::bytesPerSample; } | |||||
| /** Returns the number of interleaved channels in the format. */ | |||||
| int getNumInterleavedChannels() const noexcept { return (int) this->numInterleavedChannels; } | |||||
| /** Returns the number of bytes between the start address of each sample. */ | |||||
| int getNumBytesBetweenSamples() const noexcept { return InterleavingType::getNumBytesBetweenSamples (data); } | |||||
| /** Returns the accuracy of this format when represented as a 32-bit integer. | |||||
| This is the smallest number above 0 that can be represented in the sample format, converted to | |||||
| a 32-bit range. E,g. if the format is 8-bit, its resolution is 0x01000000; if the format is 24-bit, | |||||
| its resolution is 0x100. | |||||
| */ | |||||
| static int get32BitResolution() noexcept { return (int) SampleFormat::resolution; } | |||||
| /** Returns a pointer to the underlying data. */ | |||||
| const void* getRawData() const noexcept { return data.data; } | |||||
| private: | |||||
| //============================================================================== | |||||
| SampleFormat data; | |||||
| inline void advance() noexcept { this->advanceData (data); } | |||||
| Pointer operator++ (int); // private to force you to use the more efficient pre-increment! | |||||
| Pointer operator-- (int); | |||||
| }; | |||||
| //============================================================================== | |||||
| /** A base class for objects that are used to convert between two different sample formats. | |||||
| The AudioData::ConverterInstance implements this base class and can be templated, so | |||||
| you can create an instance that converts between two particular formats, and then | |||||
| store this in the abstract base class. | |||||
| @see AudioData::ConverterInstance | |||||
| */ | |||||
| class Converter | |||||
| { | |||||
| public: | |||||
| virtual ~Converter() {} | |||||
| /** Converts a sequence of samples from the converter's source format into the dest format. */ | |||||
| virtual void convertSamples (void* destSamples, const void* sourceSamples, int numSamples) const = 0; | |||||
| /** Converts a sequence of samples from the converter's source format into the dest format. | |||||
| This method takes sub-channel indexes, which can be used with interleaved formats in order to choose a | |||||
| particular sub-channel of the data to be used. | |||||
| */ | |||||
| virtual void convertSamples (void* destSamples, int destSubChannel, | |||||
| const void* sourceSamples, int sourceSubChannel, int numSamples) const = 0; | |||||
| }; | |||||
| //============================================================================== | |||||
| /** | |||||
| A class that converts between two templated AudioData::Pointer types, and which | |||||
| implements the AudioData::Converter interface. | |||||
| This can be used as a concrete instance of the AudioData::Converter abstract class. | |||||
| @see AudioData::Converter | |||||
| */ | |||||
| template <class SourceSampleType, class DestSampleType> | |||||
| class ConverterInstance : public Converter | |||||
| { | |||||
| public: | |||||
| ConverterInstance (int numSourceChannels = 1, int numDestChannels = 1) | |||||
| : sourceChannels (numSourceChannels), destChannels (numDestChannels) | |||||
| {} | |||||
| void convertSamples (void* dest, const void* source, int numSamples) const override | |||||
| { | |||||
| SourceSampleType s (source, sourceChannels); | |||||
| DestSampleType d (dest, destChannels); | |||||
| d.convertSamples (s, numSamples); | |||||
| } | |||||
| void convertSamples (void* dest, int destSubChannel, | |||||
| const void* source, int sourceSubChannel, int numSamples) const override | |||||
| { | |||||
| jassert (destSubChannel < destChannels && sourceSubChannel < sourceChannels); | |||||
| SourceSampleType s (addBytesToPointer (source, sourceSubChannel * SourceSampleType::getBytesPerSample()), sourceChannels); | |||||
| DestSampleType d (addBytesToPointer (dest, destSubChannel * DestSampleType::getBytesPerSample()), destChannels); | |||||
| d.convertSamples (s, numSamples); | |||||
| } | |||||
| private: | |||||
| const int sourceChannels, destChannels; | |||||
| CARLA_DECLARE_NON_COPY_CLASS (ConverterInstance) | |||||
| }; | |||||
| }; | |||||
| //============================================================================== | |||||
| /** | |||||
| A set of routines to convert buffers of 32-bit floating point data to and from | |||||
| various integer formats. | |||||
| Note that these functions are deprecated - the AudioData class provides a much more | |||||
| flexible set of conversion classes now. | |||||
| */ | |||||
| class AudioDataConverters | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| static void convertFloatToInt16LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 2); | |||||
| static void convertFloatToInt16BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 2); | |||||
| static void convertFloatToInt24LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 3); | |||||
| static void convertFloatToInt24BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 3); | |||||
| static void convertFloatToInt32LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); | |||||
| static void convertFloatToInt32BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); | |||||
| static void convertFloatToFloat32LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); | |||||
| static void convertFloatToFloat32BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); | |||||
| //============================================================================== | |||||
| static void convertInt16LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 2); | |||||
| static void convertInt16BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 2); | |||||
| static void convertInt24LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 3); | |||||
| static void convertInt24BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 3); | |||||
| static void convertInt32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); | |||||
| static void convertInt32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); | |||||
| static void convertFloat32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); | |||||
| static void convertFloat32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); | |||||
| //============================================================================== | |||||
| enum DataFormat | |||||
| { | |||||
| int16LE, | |||||
| int16BE, | |||||
| int24LE, | |||||
| int24BE, | |||||
| int32LE, | |||||
| int32BE, | |||||
| float32LE, | |||||
| float32BE, | |||||
| }; | |||||
| static void convertFloatToFormat (DataFormat destFormat, | |||||
| const float* source, void* dest, int numSamples); | |||||
| static void convertFormatToFloat (DataFormat sourceFormat, | |||||
| const void* source, float* dest, int numSamples); | |||||
| //============================================================================== | |||||
| static void interleaveSamples (const float** source, float* dest, | |||||
| int numSamples, int numChannels); | |||||
| static void deinterleaveSamples (const float* source, float** dest, | |||||
| int numSamples, int numChannels); | |||||
| private: | |||||
| AudioDataConverters(); | |||||
| CARLA_DECLARE_NON_COPY_CLASS (AudioDataConverters) | |||||
| }; | |||||
| } | |||||
| #endif // WATER_AUDIODATACONVERTERS_H_INCLUDED | |||||
| @@ -160,7 +160,7 @@ uint32* BigInteger::ensureSize (const size_t numVals) noexcept | |||||
| //============================================================================== | //============================================================================== | ||||
| bool BigInteger::operator[] (const int bit) const noexcept | bool BigInteger::operator[] (const int bit) const noexcept | ||||
| { | { | ||||
| if (bit < 0 || bit > highestBit) | |||||
| if (bit > highestBit || bit < 0) | |||||
| return false; | return false; | ||||
| if (const uint32* const values = getValues()) | if (const uint32* const values = getValues()) | ||||
| @@ -182,9 +182,6 @@ void BigInteger::clear() noexcept | |||||
| bool BigInteger::setBit (const int bit) noexcept | bool BigInteger::setBit (const int bit) noexcept | ||||
| { | { | ||||
| if (bit < 0) | |||||
| return false; | |||||
| CARLA_SAFE_ASSERT_RETURN(bit >= 0, false); | CARLA_SAFE_ASSERT_RETURN(bit >= 0, false); | ||||
| if (bit > highestBit) | if (bit > highestBit) | ||||
| @@ -216,8 +213,7 @@ bool BigInteger::setBit (const int bit, const bool shouldBeSet) noexcept | |||||
| bool BigInteger::clearBit (const int bit) noexcept | bool BigInteger::clearBit (const int bit) noexcept | ||||
| { | { | ||||
| if (bit < 0 || bit > highestBit) | |||||
| return false; | |||||
| CARLA_SAFE_ASSERT_RETURN(bit <= highestBit && bit >= 0, false); | |||||
| uint32* const values = getValues(); | uint32* const values = getValues(); | ||||
| CARLA_SAFE_ASSERT_RETURN(values != nullptr, false); | CARLA_SAFE_ASSERT_RETURN(values != nullptr, false); | ||||
| @@ -0,0 +1,150 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the Water library. | |||||
| Copyright (c) 2016 ROLI Ltd. | |||||
| Copyright (C) 2018 Filipe Coelho <falktx@falktx.com> | |||||
| Permission is granted to use this software under the terms of the ISC license | |||||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
| Permission to use, copy, modify, and/or distribute this software for any | |||||
| purpose with or without fee is hereby granted, provided that the above | |||||
| copyright notice and this permission notice appear in all copies. | |||||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
| OF THIS SOFTWARE. | |||||
| ============================================================================== | |||||
| */ | |||||
| #include "StringPairArray.h" | |||||
| namespace water { | |||||
| StringPairArray::StringPairArray (const bool ignoreCase_) | |||||
| : ignoreCase (ignoreCase_) | |||||
| { | |||||
| } | |||||
| StringPairArray::StringPairArray (const StringPairArray& other) | |||||
| : keys (other.keys), | |||||
| values (other.values), | |||||
| ignoreCase (other.ignoreCase) | |||||
| { | |||||
| } | |||||
| StringPairArray::~StringPairArray() | |||||
| { | |||||
| } | |||||
| StringPairArray& StringPairArray::operator= (const StringPairArray& other) | |||||
| { | |||||
| keys = other.keys; | |||||
| values = other.values; | |||||
| return *this; | |||||
| } | |||||
| bool StringPairArray::operator== (const StringPairArray& other) const | |||||
| { | |||||
| for (int i = keys.size(); --i >= 0;) | |||||
| if (other [keys[i]] != values[i]) | |||||
| return false; | |||||
| return true; | |||||
| } | |||||
| bool StringPairArray::operator!= (const StringPairArray& other) const | |||||
| { | |||||
| return ! operator== (other); | |||||
| } | |||||
| const String& StringPairArray::operator[] (StringRef key) const | |||||
| { | |||||
| return values [keys.indexOf (key, ignoreCase)]; | |||||
| } | |||||
| String StringPairArray::getValue (StringRef key, const String& defaultReturnValue) const | |||||
| { | |||||
| const int i = keys.indexOf (key, ignoreCase); | |||||
| if (i >= 0) | |||||
| return values[i]; | |||||
| return defaultReturnValue; | |||||
| } | |||||
| bool StringPairArray::containsKey (StringRef key) const noexcept | |||||
| { | |||||
| return keys.contains (key); | |||||
| } | |||||
| void StringPairArray::set (const String& key, const String& value) | |||||
| { | |||||
| const int i = keys.indexOf (key, ignoreCase); | |||||
| if (i >= 0) | |||||
| { | |||||
| values.set (i, value); | |||||
| } | |||||
| else | |||||
| { | |||||
| keys.add (key); | |||||
| values.add (value); | |||||
| } | |||||
| } | |||||
| void StringPairArray::addArray (const StringPairArray& other) | |||||
| { | |||||
| for (int i = 0; i < other.size(); ++i) | |||||
| set (other.keys[i], other.values[i]); | |||||
| } | |||||
| void StringPairArray::clear() | |||||
| { | |||||
| keys.clear(); | |||||
| values.clear(); | |||||
| } | |||||
| void StringPairArray::remove (StringRef key) | |||||
| { | |||||
| remove (keys.indexOf (key, ignoreCase)); | |||||
| } | |||||
| void StringPairArray::remove (const int index) | |||||
| { | |||||
| keys.remove (index); | |||||
| values.remove (index); | |||||
| } | |||||
| void StringPairArray::setIgnoresCase (const bool shouldIgnoreCase) | |||||
| { | |||||
| ignoreCase = shouldIgnoreCase; | |||||
| } | |||||
| String StringPairArray::getDescription() const | |||||
| { | |||||
| String s; | |||||
| for (int i = 0; i < keys.size(); ++i) | |||||
| { | |||||
| s << keys[i] << " = " << values[i]; | |||||
| if (i < keys.size()) | |||||
| s << ", "; | |||||
| } | |||||
| return s; | |||||
| } | |||||
| void StringPairArray::minimiseStorageOverheads() | |||||
| { | |||||
| keys.minimiseStorageOverheads(); | |||||
| values.minimiseStorageOverheads(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,165 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the Water library. | |||||
| Copyright (c) 2016 ROLI Ltd. | |||||
| Copyright (C) 2018 Filipe Coelho <falktx@falktx.com> | |||||
| Permission is granted to use this software under the terms of the ISC license | |||||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
| Permission to use, copy, modify, and/or distribute this software for any | |||||
| purpose with or without fee is hereby granted, provided that the above | |||||
| copyright notice and this permission notice appear in all copies. | |||||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
| OF THIS SOFTWARE. | |||||
| ----------------------------------------------------------------------------- | |||||
| To release a closed-source product which uses other parts of JUCE not | |||||
| licensed under the ISC terms, commercial licenses are available: visit | |||||
| www.juce.com for more information. | |||||
| ============================================================================== | |||||
| */ | |||||
| #ifndef JUCE_STRINGPAIRARRAY_H_INCLUDED | |||||
| #define JUCE_STRINGPAIRARRAY_H_INCLUDED | |||||
| #include "StringArray.h" | |||||
| #include "StringRef.h" | |||||
| namespace water { | |||||
| //============================================================================== | |||||
| /** | |||||
| A container for holding a set of strings which are keyed by another string. | |||||
| @see StringArray | |||||
| */ | |||||
| class StringPairArray | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Creates an empty array */ | |||||
| StringPairArray (bool ignoreCaseWhenComparingKeys = true); | |||||
| /** Creates a copy of another array */ | |||||
| StringPairArray (const StringPairArray& other); | |||||
| /** Destructor. */ | |||||
| ~StringPairArray(); | |||||
| /** Copies the contents of another string array into this one */ | |||||
| StringPairArray& operator= (const StringPairArray& other); | |||||
| //============================================================================== | |||||
| /** Compares two arrays. | |||||
| Comparisons are case-sensitive. | |||||
| @returns true only if the other array contains exactly the same strings with the same keys | |||||
| */ | |||||
| bool operator== (const StringPairArray& other) const; | |||||
| /** Compares two arrays. | |||||
| Comparisons are case-sensitive. | |||||
| @returns false if the other array contains exactly the same strings with the same keys | |||||
| */ | |||||
| bool operator!= (const StringPairArray& other) const; | |||||
| //============================================================================== | |||||
| /** Finds the value corresponding to a key string. | |||||
| If no such key is found, this will just return an empty string. To check whether | |||||
| a given key actually exists (because it might actually be paired with an empty string), use | |||||
| the getAllKeys() method to obtain a list. | |||||
| Obviously the reference returned shouldn't be stored for later use, as the | |||||
| string it refers to may disappear when the array changes. | |||||
| @see getValue | |||||
| */ | |||||
| const String& operator[] (StringRef key) const; | |||||
| /** Finds the value corresponding to a key string. | |||||
| If no such key is found, this will just return the value provided as a default. | |||||
| @see operator[] | |||||
| */ | |||||
| String getValue (StringRef, const String& defaultReturnValue) const; | |||||
| /** Returns true if the given key exists. */ | |||||
| bool containsKey (StringRef key) const noexcept; | |||||
| /** Returns a list of all keys in the array. */ | |||||
| const StringArray& getAllKeys() const noexcept { return keys; } | |||||
| /** Returns a list of all values in the array. */ | |||||
| const StringArray& getAllValues() const noexcept { return values; } | |||||
| /** Returns the number of strings in the array */ | |||||
| inline int size() const noexcept { return keys.size(); }; | |||||
| //============================================================================== | |||||
| /** Adds or amends a key/value pair. | |||||
| If a value already exists with this key, its value will be overwritten, | |||||
| otherwise the key/value pair will be added to the array. | |||||
| */ | |||||
| void set (const String& key, const String& value); | |||||
| /** Adds the items from another array to this one. | |||||
| This is equivalent to using set() to add each of the pairs from the other array. | |||||
| */ | |||||
| void addArray (const StringPairArray& other); | |||||
| //============================================================================== | |||||
| /** Removes all elements from the array. */ | |||||
| void clear(); | |||||
| /** Removes a string from the array based on its key. | |||||
| If the key isn't found, nothing will happen. | |||||
| */ | |||||
| void remove (StringRef key); | |||||
| /** Removes a string from the array based on its index. | |||||
| If the index is out-of-range, no action will be taken. | |||||
| */ | |||||
| void remove (int index); | |||||
| //============================================================================== | |||||
| /** Indicates whether to use a case-insensitive search when looking up a key string. | |||||
| */ | |||||
| void setIgnoresCase (bool shouldIgnoreCase); | |||||
| //============================================================================== | |||||
| /** Returns a descriptive string containing the items. | |||||
| This is handy for dumping the contents of an array. | |||||
| */ | |||||
| String getDescription() const; | |||||
| //============================================================================== | |||||
| /** Reduces the amount of storage being used by the array. | |||||
| Arrays typically allocate slightly more storage than they need, and after | |||||
| removing elements, they may have quite a lot of unused space allocated. | |||||
| This method will reduce the amount of allocated storage to a minimum. | |||||
| */ | |||||
| void minimiseStorageOverheads(); | |||||
| private: | |||||
| //============================================================================== | |||||
| StringArray keys, values; | |||||
| bool ignoreCase; | |||||
| CARLA_LEAK_DETECTOR (StringPairArray) | |||||
| }; | |||||
| } | |||||
| #endif // JUCE_STRINGPAIRARRAY_H_INCLUDED | |||||
| @@ -36,6 +36,14 @@ HINSTANCE water_getCurrentModuleInstanceHandle() noexcept | |||||
| } | } | ||||
| #include "audiocodecs/WavAudioFormat.cpp" | |||||
| #include "audioformat/AudioFormat.cpp" | |||||
| #include "audioformat/AudioFormatManager.cpp" | |||||
| #include "audioformat/AudioFormatReader.cpp" | |||||
| #include "buffers/AudioDataConverters.cpp" | |||||
| #include "containers/NamedValueSet.cpp" | #include "containers/NamedValueSet.cpp" | ||||
| #include "containers/Variant.cpp" | #include "containers/Variant.cpp" | ||||
| @@ -72,6 +80,7 @@ HINSTANCE water_getCurrentModuleInstanceHandle() noexcept | |||||
| #include "text/CharacterFunctions.cpp" | #include "text/CharacterFunctions.cpp" | ||||
| #include "text/Identifier.cpp" | #include "text/Identifier.cpp" | ||||
| #include "text/StringArray.cpp" | #include "text/StringArray.cpp" | ||||
| #include "text/StringPairArray.cpp" | |||||
| #include "text/StringPool.cpp" | #include "text/StringPool.cpp" | ||||
| #include "text/String.cpp" | #include "text/String.cpp" | ||||
| @@ -84,6 +84,8 @@ | |||||
| namespace water | namespace water | ||||
| { | { | ||||
| class AudioFormatManager; | |||||
| class AudioFormatReader; | |||||
| class AudioProcessor; | class AudioProcessor; | ||||
| class AudioSampleBuffer; | class AudioSampleBuffer; | ||||
| class File; | class File; | ||||