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; | |||