|
|
|
@@ -161,6 +161,91 @@ const char* const WavAudioFormat::riffInfoWatermarkURL = "IWMU"; |
|
|
|
const char* const WavAudioFormat::riffInfoWrittenBy = "IWRI";
|
|
|
|
const char* const WavAudioFormat::riffInfoYear = "YEAR";
|
|
|
|
|
|
|
|
const char* const WavAudioFormat::aswgContentType = "contentType";
|
|
|
|
const char* const WavAudioFormat::aswgProject = "project";
|
|
|
|
const char* const WavAudioFormat::aswgOriginator = "originator";
|
|
|
|
const char* const WavAudioFormat::aswgOriginatorStudio = "originatorStudio";
|
|
|
|
const char* const WavAudioFormat::aswgNotes = "notes";
|
|
|
|
const char* const WavAudioFormat::aswgSession = "session";
|
|
|
|
const char* const WavAudioFormat::aswgState = "state";
|
|
|
|
const char* const WavAudioFormat::aswgEditor = "editor";
|
|
|
|
const char* const WavAudioFormat::aswgMixer = "mixer";
|
|
|
|
const char* const WavAudioFormat::aswgFxChainName = "fxChainName";
|
|
|
|
const char* const WavAudioFormat::aswgChannelConfig = "channelConfig";
|
|
|
|
const char* const WavAudioFormat::aswgAmbisonicFormat = "ambisonicFormat";
|
|
|
|
const char* const WavAudioFormat::aswgAmbisonicChnOrder = "ambisonicChnOrder";
|
|
|
|
const char* const WavAudioFormat::aswgAmbisonicNorm = "ambisonicNorm";
|
|
|
|
const char* const WavAudioFormat::aswgMicType = "micType";
|
|
|
|
const char* const WavAudioFormat::aswgMicConfig = "micConfig";
|
|
|
|
const char* const WavAudioFormat::aswgMicDistance = "micDistance";
|
|
|
|
const char* const WavAudioFormat::aswgRecordingLoc = "recordingLoc";
|
|
|
|
const char* const WavAudioFormat::aswgIsDesigned = "isDesigned";
|
|
|
|
const char* const WavAudioFormat::aswgRecEngineer = "recEngineer";
|
|
|
|
const char* const WavAudioFormat::aswgRecStudio = "recStudio";
|
|
|
|
const char* const WavAudioFormat::aswgImpulseLocation = "impulseLocation";
|
|
|
|
const char* const WavAudioFormat::aswgCategory = "category";
|
|
|
|
const char* const WavAudioFormat::aswgSubCategory = "subCategory";
|
|
|
|
const char* const WavAudioFormat::aswgCatId = "catId";
|
|
|
|
const char* const WavAudioFormat::aswgUserCategory = "userCategory";
|
|
|
|
const char* const WavAudioFormat::aswgUserData = "userData";
|
|
|
|
const char* const WavAudioFormat::aswgVendorCategory = "vendorCategory";
|
|
|
|
const char* const WavAudioFormat::aswgFxName = "fxName";
|
|
|
|
const char* const WavAudioFormat::aswgLibrary = "library";
|
|
|
|
const char* const WavAudioFormat::aswgCreatorId = "creatorId";
|
|
|
|
const char* const WavAudioFormat::aswgSourceId = "sourceId";
|
|
|
|
const char* const WavAudioFormat::aswgRmsPower = "rmsPower";
|
|
|
|
const char* const WavAudioFormat::aswgLoudness = "loudness";
|
|
|
|
const char* const WavAudioFormat::aswgLoudnessRange = "loudnessRange";
|
|
|
|
const char* const WavAudioFormat::aswgMaxPeak = "maxPeak";
|
|
|
|
const char* const WavAudioFormat::aswgSpecDensity = "specDensity";
|
|
|
|
const char* const WavAudioFormat::aswgZeroCrossRate = "zeroCrossRate";
|
|
|
|
const char* const WavAudioFormat::aswgPapr = "papr";
|
|
|
|
const char* const WavAudioFormat::aswgText = "text";
|
|
|
|
const char* const WavAudioFormat::aswgEfforts = "efforts";
|
|
|
|
const char* const WavAudioFormat::aswgEffortType = "effortType";
|
|
|
|
const char* const WavAudioFormat::aswgProjection = "projection";
|
|
|
|
const char* const WavAudioFormat::aswgLanguage = "language";
|
|
|
|
const char* const WavAudioFormat::aswgTimingRestriction = "timingRestriction";
|
|
|
|
const char* const WavAudioFormat::aswgCharacterName = "characterName";
|
|
|
|
const char* const WavAudioFormat::aswgCharacterGender = "characterGender";
|
|
|
|
const char* const WavAudioFormat::aswgCharacterAge = "characterAge";
|
|
|
|
const char* const WavAudioFormat::aswgCharacterRole = "characterRole";
|
|
|
|
const char* const WavAudioFormat::aswgActorName = "actorName";
|
|
|
|
const char* const WavAudioFormat::aswgActorGender = "actorGender";
|
|
|
|
const char* const WavAudioFormat::aswgDirector = "director";
|
|
|
|
const char* const WavAudioFormat::aswgDirection = "direction";
|
|
|
|
const char* const WavAudioFormat::aswgFxUsed = "fxUsed";
|
|
|
|
const char* const WavAudioFormat::aswgUsageRights = "usageRights";
|
|
|
|
const char* const WavAudioFormat::aswgIsUnion = "isUnion";
|
|
|
|
const char* const WavAudioFormat::aswgAccent = "accent";
|
|
|
|
const char* const WavAudioFormat::aswgEmotion = "emotion";
|
|
|
|
const char* const WavAudioFormat::aswgComposor = "composor";
|
|
|
|
const char* const WavAudioFormat::aswgArtist = "artist";
|
|
|
|
const char* const WavAudioFormat::aswgSongTitle = "songTitle";
|
|
|
|
const char* const WavAudioFormat::aswgGenre = "genre";
|
|
|
|
const char* const WavAudioFormat::aswgSubGenre = "subGenre";
|
|
|
|
const char* const WavAudioFormat::aswgProducer = "producer";
|
|
|
|
const char* const WavAudioFormat::aswgMusicSup = "musicSup";
|
|
|
|
const char* const WavAudioFormat::aswgInstrument = "instrument";
|
|
|
|
const char* const WavAudioFormat::aswgMusicPublisher = "musicPublisher";
|
|
|
|
const char* const WavAudioFormat::aswgRightsOwner = "rightsOwner";
|
|
|
|
const char* const WavAudioFormat::aswgIsSource = "isSource";
|
|
|
|
const char* const WavAudioFormat::aswgIsLoop = "isLoop";
|
|
|
|
const char* const WavAudioFormat::aswgIntensity = "intensity";
|
|
|
|
const char* const WavAudioFormat::aswgIsFinal = "isFinal";
|
|
|
|
const char* const WavAudioFormat::aswgOrderRef = "orderRef";
|
|
|
|
const char* const WavAudioFormat::aswgIsOst = "isOst";
|
|
|
|
const char* const WavAudioFormat::aswgIsCinematic = "isCinematic";
|
|
|
|
const char* const WavAudioFormat::aswgIsLicensed = "isLicensed";
|
|
|
|
const char* const WavAudioFormat::aswgIsDiegetic = "isDiegetic";
|
|
|
|
const char* const WavAudioFormat::aswgMusicVersion = "musicVersion";
|
|
|
|
const char* const WavAudioFormat::aswgIsrcId = "isrcId";
|
|
|
|
const char* const WavAudioFormat::aswgTempo = "tempo";
|
|
|
|
const char* const WavAudioFormat::aswgTimeSig = "timeSig";
|
|
|
|
const char* const WavAudioFormat::aswgInKey = "inKey";
|
|
|
|
const char* const WavAudioFormat::aswgBillingCode = "billingCode";
|
|
|
|
const char* const WavAudioFormat::aswgVersion = "IXML_VERSION";
|
|
|
|
|
|
|
|
const char* const WavAudioFormat::ISRC = "ISRC";
|
|
|
|
const char* const WavAudioFormat::internationalStandardRecordingCode = "international standard recording code";
|
|
|
|
const char* const WavAudioFormat::tracktionLoopInfo = "tracktion loop info";
|
|
|
|
@@ -856,6 +941,157 @@ namespace WavFileHelpers |
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
//=============================================================================
|
|
|
|
namespace IXMLChunk
|
|
|
|
{
|
|
|
|
static const std::unordered_set<String> aswgMetadataKeys
|
|
|
|
{
|
|
|
|
WavAudioFormat::aswgContentType,
|
|
|
|
WavAudioFormat::aswgProject,
|
|
|
|
WavAudioFormat::aswgOriginator,
|
|
|
|
WavAudioFormat::aswgOriginatorStudio,
|
|
|
|
WavAudioFormat::aswgNotes,
|
|
|
|
WavAudioFormat::aswgSession,
|
|
|
|
WavAudioFormat::aswgState,
|
|
|
|
WavAudioFormat::aswgEditor,
|
|
|
|
WavAudioFormat::aswgMixer,
|
|
|
|
WavAudioFormat::aswgFxChainName,
|
|
|
|
WavAudioFormat::aswgChannelConfig,
|
|
|
|
WavAudioFormat::aswgAmbisonicFormat,
|
|
|
|
WavAudioFormat::aswgAmbisonicChnOrder,
|
|
|
|
WavAudioFormat::aswgAmbisonicNorm,
|
|
|
|
WavAudioFormat::aswgMicType,
|
|
|
|
WavAudioFormat::aswgMicConfig,
|
|
|
|
WavAudioFormat::aswgMicDistance,
|
|
|
|
WavAudioFormat::aswgRecordingLoc,
|
|
|
|
WavAudioFormat::aswgIsDesigned,
|
|
|
|
WavAudioFormat::aswgRecEngineer,
|
|
|
|
WavAudioFormat::aswgRecStudio,
|
|
|
|
WavAudioFormat::aswgImpulseLocation,
|
|
|
|
WavAudioFormat::aswgCategory,
|
|
|
|
WavAudioFormat::aswgSubCategory,
|
|
|
|
WavAudioFormat::aswgCatId,
|
|
|
|
WavAudioFormat::aswgUserCategory,
|
|
|
|
WavAudioFormat::aswgUserData,
|
|
|
|
WavAudioFormat::aswgVendorCategory,
|
|
|
|
WavAudioFormat::aswgFxName,
|
|
|
|
WavAudioFormat::aswgLibrary,
|
|
|
|
WavAudioFormat::aswgCreatorId,
|
|
|
|
WavAudioFormat::aswgSourceId,
|
|
|
|
WavAudioFormat::aswgRmsPower,
|
|
|
|
WavAudioFormat::aswgLoudness,
|
|
|
|
WavAudioFormat::aswgLoudnessRange,
|
|
|
|
WavAudioFormat::aswgMaxPeak,
|
|
|
|
WavAudioFormat::aswgSpecDensity,
|
|
|
|
WavAudioFormat::aswgZeroCrossRate,
|
|
|
|
WavAudioFormat::aswgPapr,
|
|
|
|
WavAudioFormat::aswgText,
|
|
|
|
WavAudioFormat::aswgEfforts,
|
|
|
|
WavAudioFormat::aswgEffortType,
|
|
|
|
WavAudioFormat::aswgProjection,
|
|
|
|
WavAudioFormat::aswgLanguage,
|
|
|
|
WavAudioFormat::aswgTimingRestriction,
|
|
|
|
WavAudioFormat::aswgCharacterName,
|
|
|
|
WavAudioFormat::aswgCharacterGender,
|
|
|
|
WavAudioFormat::aswgCharacterAge,
|
|
|
|
WavAudioFormat::aswgCharacterRole,
|
|
|
|
WavAudioFormat::aswgActorName,
|
|
|
|
WavAudioFormat::aswgActorGender,
|
|
|
|
WavAudioFormat::aswgDirector,
|
|
|
|
WavAudioFormat::aswgDirection,
|
|
|
|
WavAudioFormat::aswgFxUsed,
|
|
|
|
WavAudioFormat::aswgUsageRights,
|
|
|
|
WavAudioFormat::aswgIsUnion,
|
|
|
|
WavAudioFormat::aswgAccent,
|
|
|
|
WavAudioFormat::aswgEmotion,
|
|
|
|
WavAudioFormat::aswgComposor,
|
|
|
|
WavAudioFormat::aswgArtist,
|
|
|
|
WavAudioFormat::aswgSongTitle,
|
|
|
|
WavAudioFormat::aswgGenre,
|
|
|
|
WavAudioFormat::aswgSubGenre,
|
|
|
|
WavAudioFormat::aswgProducer,
|
|
|
|
WavAudioFormat::aswgMusicSup,
|
|
|
|
WavAudioFormat::aswgInstrument,
|
|
|
|
WavAudioFormat::aswgMusicPublisher,
|
|
|
|
WavAudioFormat::aswgRightsOwner,
|
|
|
|
WavAudioFormat::aswgIsSource,
|
|
|
|
WavAudioFormat::aswgIsLoop,
|
|
|
|
WavAudioFormat::aswgIntensity,
|
|
|
|
WavAudioFormat::aswgIsFinal,
|
|
|
|
WavAudioFormat::aswgOrderRef,
|
|
|
|
WavAudioFormat::aswgIsOst,
|
|
|
|
WavAudioFormat::aswgIsCinematic,
|
|
|
|
WavAudioFormat::aswgIsLicensed,
|
|
|
|
WavAudioFormat::aswgIsDiegetic,
|
|
|
|
WavAudioFormat::aswgMusicVersion,
|
|
|
|
WavAudioFormat::aswgIsrcId,
|
|
|
|
WavAudioFormat::aswgTempo,
|
|
|
|
WavAudioFormat::aswgTimeSig,
|
|
|
|
WavAudioFormat::aswgInKey,
|
|
|
|
WavAudioFormat::aswgBillingCode
|
|
|
|
};
|
|
|
|
|
|
|
|
static void addToMetadata (StringMap& destValues, const String& source)
|
|
|
|
{
|
|
|
|
if (auto xml = parseXML (source))
|
|
|
|
{
|
|
|
|
if (xml->hasTagName ("BWFXML"))
|
|
|
|
{
|
|
|
|
if (const auto* entry = xml->getChildByName (WavAudioFormat::aswgVersion))
|
|
|
|
destValues[WavAudioFormat::aswgVersion] = entry->getAllSubText();
|
|
|
|
|
|
|
|
if (const auto* aswgElement = xml->getChildByName ("ASWG"))
|
|
|
|
{
|
|
|
|
for (const auto* entry : aswgElement->getChildIterator())
|
|
|
|
{
|
|
|
|
const auto& tag = entry->getTagName();
|
|
|
|
|
|
|
|
if (aswgMetadataKeys.find (tag) != aswgMetadataKeys.end())
|
|
|
|
destValues[tag] = entry->getAllSubText();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static MemoryBlock createFrom (const StringMap& values)
|
|
|
|
{
|
|
|
|
auto createTextElement = [] (const StringRef& key, const StringRef& value)
|
|
|
|
{
|
|
|
|
auto* elem = new XmlElement (key);
|
|
|
|
elem->addTextElement (value);
|
|
|
|
return elem;
|
|
|
|
};
|
|
|
|
|
|
|
|
std::unique_ptr<XmlElement> aswgElement;
|
|
|
|
|
|
|
|
for (const auto& pair : values)
|
|
|
|
{
|
|
|
|
if (aswgMetadataKeys.find (pair.first) != aswgMetadataKeys.end())
|
|
|
|
{
|
|
|
|
if (aswgElement == nullptr)
|
|
|
|
aswgElement = std::make_unique<XmlElement> ("ASWG");
|
|
|
|
|
|
|
|
aswgElement->addChildElement (createTextElement (pair.first, pair.second));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
MemoryOutputStream outputStream;
|
|
|
|
|
|
|
|
if (aswgElement != nullptr)
|
|
|
|
{
|
|
|
|
XmlElement xml ("BWFXML");
|
|
|
|
auto aswgVersion = getValueWithDefault (values, WavAudioFormat::aswgVersion, "3.01");
|
|
|
|
xml.addChildElement (createTextElement (WavAudioFormat::aswgVersion, aswgVersion));
|
|
|
|
xml.addChildElement (aswgElement.release());
|
|
|
|
xml.writeTo (outputStream);
|
|
|
|
outputStream.writeRepeatedByte (0, outputStream.getDataSize());
|
|
|
|
}
|
|
|
|
|
|
|
|
return outputStream.getMemoryBlock();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
namespace AXMLChunk
|
|
|
|
{
|
|
|
|
@@ -1136,6 +1372,12 @@ public: |
|
|
|
input->readIntoMemoryBlock (axml, (ssize_t) length);
|
|
|
|
AXMLChunk::addToMetadata (dict, axml.toString());
|
|
|
|
}
|
|
|
|
else if (chunkType == chunkName ("iXML"))
|
|
|
|
{
|
|
|
|
MemoryBlock ixml;
|
|
|
|
input->readIntoMemoryBlock (ixml, (ssize_t) length);
|
|
|
|
IXMLChunk::addToMetadata (dict, ixml.toString());
|
|
|
|
}
|
|
|
|
else if (chunkType == chunkName ("LIST"))
|
|
|
|
{
|
|
|
|
auto subChunkType = input->readInt();
|
|
|
|
@@ -1350,6 +1592,7 @@ public: |
|
|
|
const auto map = toMap (metadataValues);
|
|
|
|
|
|
|
|
bwavChunk = BWAVChunk::createFrom (map);
|
|
|
|
ixmlChunk = IXMLChunk::createFrom (map);
|
|
|
|
axmlChunk = AXMLChunk::createFrom (map);
|
|
|
|
smplChunk = SMPLChunk::createFrom (map);
|
|
|
|
instChunk = InstChunk::createFrom (map);
|
|
|
|
@@ -1420,7 +1663,7 @@ public: |
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
MemoryBlock tempBlock, bwavChunk, axmlChunk, smplChunk, instChunk, cueChunk, listChunk, listInfoChunk, acidChunk, trckChunk;
|
|
|
|
MemoryBlock tempBlock, bwavChunk, ixmlChunk, axmlChunk, smplChunk, instChunk, cueChunk, listChunk, listInfoChunk, acidChunk, trckChunk;
|
|
|
|
uint64 lengthInSamples = 0, bytesWritten = 0;
|
|
|
|
int64 headerPosition = 0;
|
|
|
|
bool writeFailed = false;
|
|
|
|
@@ -1450,6 +1693,7 @@ private: |
|
|
|
int64 riffChunkSize = (int64) (4 /* 'RIFF' */ + 8 + 40 /* WAVEFORMATEX */
|
|
|
|
+ 8 + audioDataSize + (audioDataSize & 1)
|
|
|
|
+ chunkSize (bwavChunk)
|
|
|
|
+ chunkSize (ixmlChunk)
|
|
|
|
+ chunkSize (axmlChunk)
|
|
|
|
+ chunkSize (smplChunk)
|
|
|
|
+ chunkSize (instChunk)
|
|
|
|
@@ -1532,6 +1776,7 @@ private: |
|
|
|
}
|
|
|
|
|
|
|
|
writeChunk (bwavChunk, chunkName ("bext"));
|
|
|
|
writeChunk (ixmlChunk, chunkName ("iXML"));
|
|
|
|
writeChunk (axmlChunk, chunkName ("axml"));
|
|
|
|
writeChunk (smplChunk, chunkName ("smpl"));
|
|
|
|
writeChunk (instChunk, chunkName ("inst"), 7);
|
|
|
|
@@ -1951,6 +2196,61 @@ struct WaveAudioFormatTests : public UnitTest |
|
|
|
expect (a[WavAudioFormat::riffInfoSource] == "source");
|
|
|
|
expect (a[WavAudioFormat::internationalStandardRecordingCode] == "UUVVVXXYYYYY");
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
beginTest ("Files containing ASWG metadata read and write correctly");
|
|
|
|
MemoryBlock block;
|
|
|
|
StringPairArray meta;
|
|
|
|
|
|
|
|
for (const auto& key : WavFileHelpers::IXMLChunk::aswgMetadataKeys)
|
|
|
|
meta.set (key, "Test123&<>");
|
|
|
|
|
|
|
|
{
|
|
|
|
auto writer = rawToUniquePtr (WavAudioFormat().createWriterFor (new MemoryOutputStream (block, false), 48000, 1, 32, meta, 0));
|
|
|
|
expect (writer != nullptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
expect ([&]
|
|
|
|
{
|
|
|
|
auto input = std::make_unique<MemoryInputStream> (block, false);
|
|
|
|
|
|
|
|
while (! input->isExhausted())
|
|
|
|
{
|
|
|
|
char chunkType[4] {};
|
|
|
|
auto pos = input->getPosition();
|
|
|
|
|
|
|
|
input->read (chunkType, 4);
|
|
|
|
|
|
|
|
if (memcmp (chunkType, "iXML", 4) == 0)
|
|
|
|
{
|
|
|
|
auto length = (uint32) input->readInt();
|
|
|
|
|
|
|
|
MemoryBlock xmlBlock;
|
|
|
|
input->readIntoMemoryBlock (xmlBlock, (ssize_t) length);
|
|
|
|
|
|
|
|
return parseXML (xmlBlock.toString()) != nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
input->setPosition (pos + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}());
|
|
|
|
|
|
|
|
{
|
|
|
|
auto reader = rawToUniquePtr (WavAudioFormat().createReaderFor (new MemoryInputStream (block, false), true));
|
|
|
|
expect (reader != nullptr);
|
|
|
|
|
|
|
|
for (const auto& key : meta.getAllKeys())
|
|
|
|
{
|
|
|
|
const auto oldValue = meta.getValue (key, "!");
|
|
|
|
const auto newValue = reader->metadataValues.getValue (key, "");
|
|
|
|
expectEquals (oldValue, newValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
expect (reader->metadataValues.getValue (WavAudioFormat::aswgVersion, "") == "3.01");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
|