From c1a3cc28fd33e9f54a0924329384a35283de11d4 Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 7 Feb 2022 14:13:55 +0000 Subject: [PATCH] WavAudioFormat: Disambiguate ISRC and source information --- .../codecs/juce_WavAudioFormat.cpp | 138 +++++++++++++++--- .../codecs/juce_WavAudioFormat.h | 4 + 2 files changed, 125 insertions(+), 17 deletions(-) diff --git a/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp b/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp index 8414d5c873..aa2ef4ffa9 100644 --- a/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp +++ b/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp @@ -168,8 +168,9 @@ 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"; +const char* const WavAudioFormat::ISRC = "ISRC"; +const char* const WavAudioFormat::internationalStandardRecordingCode = "international standard recording code"; +const char* const WavAudioFormat::tracktionLoopInfo = "tracktion loop info"; //============================================================================== namespace WavFileHelpers @@ -880,7 +881,12 @@ namespace WavFileHelpers auto ISRCCode = xml4->getAllSubText().fromFirstOccurrenceOf ("ISRC:", false, true); if (ISRCCode.isNotEmpty()) - destValues[WavAudioFormat::ISRC] = ISRCCode; + { + // We set ISRC here for backwards compatibility. + // If the INFO 'source' field is set in the info chunk, then the + // value for this key will be overwritten later. + destValues[WavAudioFormat::riffInfoSource] = destValues[WavAudioFormat::internationalStandardRecordingCode] = ISRCCode; + } } } } @@ -890,11 +896,24 @@ namespace WavFileHelpers static MemoryBlock createFrom (const StringMap& values) { - auto ISRC = getValueWithDefault (values, WavAudioFormat::ISRC); + // Use the new ISRC key if it is present, but fall back to the + // INFO 'source' value for backwards compatibility. + auto ISRC = getValueWithDefault (values, + WavAudioFormat::internationalStandardRecordingCode, + getValueWithDefault (values, WavAudioFormat::riffInfoSource)); + MemoryOutputStream xml; if (ISRC.isNotEmpty()) { + // If you are trying to set the ISRC, make sure that you are using + // WavAudioFormat::internationalStandardRecordingCode as the metadata key, + // and that the value is 12 characters long. If you are trying to set the + // 'source' field in the INFO chunk, set the + // WavAudioFormat::internationalStandardRecordingCode metadata field to the + // empty string to silence this assertion. + jassert (ISRC.length() == 12); + xml << "" "" @@ -1869,6 +1888,8 @@ struct WaveAudioFormatTests : public UnitTest for (int i = numElementsInArray (WavFileHelpers::ListInfoChunk::types); --i >= 0;) metadataValues[WavFileHelpers::ListInfoChunk::types[i]] = WavFileHelpers::ListInfoChunk::types[i]; + metadataValues[WavAudioFormat::internationalStandardRecordingCode] = WavAudioFormat::internationalStandardRecordingCode; + if (metadataValues.size() > 0) metadataValues["MetaDataSource"] = "WAV"; @@ -1882,30 +1903,113 @@ struct WaveAudioFormatTests : public UnitTest metadataArray.addUnorderedMap (metadataValues); { - beginTest ("Creating a basic wave writer"); + beginTest ("Metadata can be written and read"); - std::unique_ptr writer (format.createWriterFor (new MemoryOutputStream (memoryBlock, false), - 44100.0, numTestAudioBufferChannels, - 32, metadataArray, 0)); - expect (writer != nullptr); + const auto newMetadata = getMetadataAfterReading (format, writeToBlock (format, metadataArray)); + expect (newMetadata == metadataArray, "Somehow, the metadata is different!"); + } - AudioBuffer buffer (numTestAudioBufferChannels, numTestAudioBufferSamples); - buffer.clear(); + { + beginTest ("Files containing a riff info source and an empty ISRC associate the source with the riffInfoSource key"); + StringPairArray meta; + meta.addMap ({ { WavAudioFormat::riffInfoSource, "customsource" }, + { WavAudioFormat::internationalStandardRecordingCode, "" } }); + const auto mb = writeToBlock (format, meta); + checkPatternsPresent (mb, { "INFOISRC" }); + checkPatternsNotPresent (mb, { "ISRC:", "writeFromAudioSampleBuffer (buffer, 0, numTestAudioBufferSamples)); + { + beginTest ("Files containing a riff info source and no ISRC associate the source with both keys " + "for backwards compatibility"); + StringPairArray meta; + meta.addMap ({ { WavAudioFormat::riffInfoSource, "customsource" } }); + const auto mb = writeToBlock (format, meta); + checkPatternsPresent (mb, { "INFOISRC", "ISRC:customsource", " reader (format.createReaderFor (new MemoryInputStream (memoryBlock, false), false)); - expect (reader != nullptr); - expect (reader->metadataValues == metadataArray, "Somehow, the metadata is different!"); + { + beginTest ("Files containing an ISRC and a riff info source associate the values with the appropriate keys"); + StringPairArray meta; + meta.addMap ({ { WavAudioFormat::riffInfoSource, "source" } }); + meta.addMap ({ { WavAudioFormat::internationalStandardRecordingCode, "UUVVVXXYYYYY" } }); + const auto mb = writeToBlock (format, meta); + checkPatternsPresent (mb, { "INFOISRC", "ISRC:UUVVVXXYYYYY", " buffer (numTestAudioBufferChannels, numTestAudioBufferSamples); + expect (writer->writeFromAudioSampleBuffer (buffer, 0, numTestAudioBufferSamples)); + } + + return mb; + } + + StringPairArray getMetadataAfterReading (WavAudioFormat& format, const MemoryBlock& mb) + { + auto reader = rawToUniquePtr (format.createReaderFor (new MemoryInputStream (mb, false), true)); + expect (reader != nullptr); + return reader->metadataValues; + } + + template + void checkPatterns (const MemoryBlock& mb, const std::vector& patterns, Fn&& fn) + { + for (const auto& pattern : patterns) + { + const auto begin = static_cast (mb.getData()); + const auto end = begin + mb.getSize(); + expect (fn (std::search (begin, end, pattern.begin(), pattern.end()), end)); + } + } + + void checkPatternsPresent (const MemoryBlock& mb, const std::vector& patterns) + { + checkPatterns (mb, patterns, std::not_equal_to<>{}); + } + + void checkPatternsNotPresent (const MemoryBlock& mb, const std::vector& patterns) + { + checkPatterns (mb, patterns, std::equal_to<>{}); + } + enum { numTestAudioBufferChannels = 2, diff --git a/modules/juce_audio_formats/codecs/juce_WavAudioFormat.h b/modules/juce_audio_formats/codecs/juce_WavAudioFormat.h index 55f5c4bf28..766d76b8cd 100644 --- a/modules/juce_audio_formats/codecs/juce_WavAudioFormat.h +++ b/modules/juce_audio_formats/codecs/juce_WavAudioFormat.h @@ -177,8 +177,12 @@ public: //============================================================================== /** Metadata property name used when reading an ISRC code from an AXML chunk. */ + [[deprecated ("This string is identical to riffInfoSource, making it impossible to differentiate between the two")]] static const char* const ISRC; + /** Metadata property name used when reading and writing ISRC codes to/from AXML chunks. */ + static const char* const internationalStandardRecordingCode; + /** Metadata property name used when reading a WAV file with a Tracktion chunk. */ static const char* const tracktionLoopInfo;