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 | |||
| { | |||
| if (bit < 0 || bit > highestBit) | |||
| if (bit > highestBit || bit < 0) | |||
| return false; | |||
| if (const uint32* const values = getValues()) | |||
| @@ -182,9 +182,6 @@ void BigInteger::clear() noexcept | |||
| bool BigInteger::setBit (const int bit) noexcept | |||
| { | |||
| if (bit < 0) | |||
| return false; | |||
| CARLA_SAFE_ASSERT_RETURN(bit >= 0, false); | |||
| if (bit > highestBit) | |||
| @@ -216,8 +213,7 @@ bool BigInteger::setBit (const int bit, const bool shouldBeSet) 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(); | |||
| 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/Variant.cpp" | |||
| @@ -72,6 +80,7 @@ HINSTANCE water_getCurrentModuleInstanceHandle() noexcept | |||
| #include "text/CharacterFunctions.cpp" | |||
| #include "text/Identifier.cpp" | |||
| #include "text/StringArray.cpp" | |||
| #include "text/StringPairArray.cpp" | |||
| #include "text/StringPool.cpp" | |||
| #include "text/String.cpp" | |||
| @@ -84,6 +84,8 @@ | |||
| namespace water | |||
| { | |||
| class AudioFormatManager; | |||
| class AudioFormatReader; | |||
| class AudioProcessor; | |||
| class AudioSampleBuffer; | |||
| class File; | |||