diff --git a/examples/DemoRunner/Builds/Android/app/CMakeLists.txt b/examples/DemoRunner/Builds/Android/app/CMakeLists.txt index 13d7014a85..db6131893f 100644 --- a/examples/DemoRunner/Builds/Android/app/CMakeLists.txt +++ b/examples/DemoRunner/Builds/Android/app/CMakeLists.txt @@ -525,6 +525,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_audio_formats/format/juce_AudioFormatReaderSource.h" "../../../../../modules/juce_audio_formats/format/juce_AudioFormatWriter.cpp" "../../../../../modules/juce_audio_formats/format/juce_AudioFormatWriter.h" + "../../../../../modules/juce_audio_formats/format/juce_AudioFormatWriterOptions.h" "../../../../../modules/juce_audio_formats/format/juce_AudioSubsectionReader.cpp" "../../../../../modules/juce_audio_formats/format/juce_AudioSubsectionReader.h" "../../../../../modules/juce_audio_formats/format/juce_BufferingAudioFormatReader.cpp" @@ -3184,6 +3185,7 @@ set_source_files_properties( "../../../../../modules/juce_audio_formats/format/juce_AudioFormatReaderSource.h" "../../../../../modules/juce_audio_formats/format/juce_AudioFormatWriter.cpp" "../../../../../modules/juce_audio_formats/format/juce_AudioFormatWriter.h" + "../../../../../modules/juce_audio_formats/format/juce_AudioFormatWriterOptions.h" "../../../../../modules/juce_audio_formats/format/juce_AudioSubsectionReader.cpp" "../../../../../modules/juce_audio_formats/format/juce_AudioSubsectionReader.h" "../../../../../modules/juce_audio_formats/format/juce_BufferingAudioFormatReader.cpp" diff --git a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj index 7599772405..5f8a3eed3e 100644 --- a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj +++ b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj @@ -3558,6 +3558,7 @@ + diff --git a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters index 24bb4fc7b8..52a5a9990f 100644 --- a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters +++ b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters @@ -4974,6 +4974,9 @@ JUCE Modules\juce_audio_formats\format + + JUCE Modules\juce_audio_formats\format + JUCE Modules\juce_audio_formats\format diff --git a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj index 6d877a27d9..a2dc7771e0 100644 --- a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj +++ b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj @@ -3558,6 +3558,7 @@ + diff --git a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters index 800afa748e..8eb54cd7f0 100644 --- a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters +++ b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters @@ -4974,6 +4974,9 @@ JUCE Modules\juce_audio_formats\format + + JUCE Modules\juce_audio_formats\format + JUCE Modules\juce_audio_formats\format diff --git a/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt b/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt index 2ccad172fb..1ae065577f 100644 --- a/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt +++ b/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt @@ -480,6 +480,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_audio_formats/format/juce_AudioFormatReaderSource.h" "../../../../../modules/juce_audio_formats/format/juce_AudioFormatWriter.cpp" "../../../../../modules/juce_audio_formats/format/juce_AudioFormatWriter.h" + "../../../../../modules/juce_audio_formats/format/juce_AudioFormatWriterOptions.h" "../../../../../modules/juce_audio_formats/format/juce_AudioSubsectionReader.cpp" "../../../../../modules/juce_audio_formats/format/juce_AudioSubsectionReader.h" "../../../../../modules/juce_audio_formats/format/juce_BufferingAudioFormatReader.cpp" @@ -2753,6 +2754,7 @@ set_source_files_properties( "../../../../../modules/juce_audio_formats/format/juce_AudioFormatReaderSource.h" "../../../../../modules/juce_audio_formats/format/juce_AudioFormatWriter.cpp" "../../../../../modules/juce_audio_formats/format/juce_AudioFormatWriter.h" + "../../../../../modules/juce_audio_formats/format/juce_AudioFormatWriterOptions.h" "../../../../../modules/juce_audio_formats/format/juce_AudioSubsectionReader.cpp" "../../../../../modules/juce_audio_formats/format/juce_AudioSubsectionReader.h" "../../../../../modules/juce_audio_formats/format/juce_BufferingAudioFormatReader.cpp" diff --git a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj index 5dabc514ed..cb12398a5a 100644 --- a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj +++ b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj @@ -3072,6 +3072,7 @@ + diff --git a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters index f5c2fa7388..db9d24942e 100644 --- a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters +++ b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters @@ -4236,6 +4236,9 @@ JUCE Modules\juce_audio_formats\format + + JUCE Modules\juce_audio_formats\format + JUCE Modules\juce_audio_formats\format diff --git a/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt b/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt index bf0710f7ba..bf8ed75274 100644 --- a/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt +++ b/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt @@ -513,6 +513,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_audio_formats/format/juce_AudioFormatReaderSource.h" "../../../../../modules/juce_audio_formats/format/juce_AudioFormatWriter.cpp" "../../../../../modules/juce_audio_formats/format/juce_AudioFormatWriter.h" + "../../../../../modules/juce_audio_formats/format/juce_AudioFormatWriterOptions.h" "../../../../../modules/juce_audio_formats/format/juce_AudioSubsectionReader.cpp" "../../../../../modules/juce_audio_formats/format/juce_AudioSubsectionReader.h" "../../../../../modules/juce_audio_formats/format/juce_BufferingAudioFormatReader.cpp" @@ -2939,6 +2940,7 @@ set_source_files_properties( "../../../../../modules/juce_audio_formats/format/juce_AudioFormatReaderSource.h" "../../../../../modules/juce_audio_formats/format/juce_AudioFormatWriter.cpp" "../../../../../modules/juce_audio_formats/format/juce_AudioFormatWriter.h" + "../../../../../modules/juce_audio_formats/format/juce_AudioFormatWriterOptions.h" "../../../../../modules/juce_audio_formats/format/juce_AudioSubsectionReader.cpp" "../../../../../modules/juce_audio_formats/format/juce_AudioSubsectionReader.h" "../../../../../modules/juce_audio_formats/format/juce_BufferingAudioFormatReader.cpp" diff --git a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj index e947c78ba5..d543831872 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj +++ b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj @@ -3252,6 +3252,7 @@ + diff --git a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters index 5d8c24d5c7..90e15a6859 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters +++ b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters @@ -4512,6 +4512,9 @@ JUCE Modules\juce_audio_formats\format + + JUCE Modules\juce_audio_formats\format + JUCE Modules\juce_audio_formats\format diff --git a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj index 3c87281588..077b98bbff 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj +++ b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj @@ -3252,6 +3252,7 @@ + diff --git a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters index 181ee2f945..c1e2985b4c 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters +++ b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters @@ -4512,6 +4512,9 @@ JUCE Modules\juce_audio_formats\format + + JUCE Modules\juce_audio_formats\format + JUCE Modules\juce_audio_formats\format diff --git a/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt b/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt index bb8244c728..1b7042adb1 100644 --- a/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt +++ b/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt @@ -484,6 +484,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_audio_formats/format/juce_AudioFormatReaderSource.h" "../../../../../modules/juce_audio_formats/format/juce_AudioFormatWriter.cpp" "../../../../../modules/juce_audio_formats/format/juce_AudioFormatWriter.h" + "../../../../../modules/juce_audio_formats/format/juce_AudioFormatWriterOptions.h" "../../../../../modules/juce_audio_formats/format/juce_AudioSubsectionReader.cpp" "../../../../../modules/juce_audio_formats/format/juce_AudioSubsectionReader.h" "../../../../../modules/juce_audio_formats/format/juce_BufferingAudioFormatReader.cpp" @@ -2837,6 +2838,7 @@ set_source_files_properties( "../../../../../modules/juce_audio_formats/format/juce_AudioFormatReaderSource.h" "../../../../../modules/juce_audio_formats/format/juce_AudioFormatWriter.cpp" "../../../../../modules/juce_audio_formats/format/juce_AudioFormatWriter.h" + "../../../../../modules/juce_audio_formats/format/juce_AudioFormatWriterOptions.h" "../../../../../modules/juce_audio_formats/format/juce_AudioSubsectionReader.cpp" "../../../../../modules/juce_audio_formats/format/juce_AudioSubsectionReader.h" "../../../../../modules/juce_audio_formats/format/juce_BufferingAudioFormatReader.cpp" diff --git a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj index 4a95e18f40..035ddec6bb 100644 --- a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj +++ b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj @@ -3163,6 +3163,7 @@ + diff --git a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters index 8a16cb8319..b6bf08b851 100644 --- a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters +++ b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters @@ -4377,6 +4377,9 @@ JUCE Modules\juce_audio_formats\format + + JUCE Modules\juce_audio_formats\format + JUCE Modules\juce_audio_formats\format diff --git a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj index 1f81c0163d..62fab9dc3c 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj +++ b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj @@ -3364,6 +3364,7 @@ + diff --git a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters index 05bc025378..3f3f2701a8 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters +++ b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters @@ -4671,6 +4671,9 @@ JUCE Modules\juce_audio_formats\format + + JUCE Modules\juce_audio_formats\format + JUCE Modules\juce_audio_formats\format diff --git a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj index f41b0d6e06..0fb1939aca 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj +++ b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj @@ -3364,6 +3364,7 @@ + diff --git a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters index 580366010d..ea787cb498 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters +++ b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters @@ -4671,6 +4671,9 @@ JUCE Modules\juce_audio_formats\format + + JUCE Modules\juce_audio_formats\format + JUCE Modules\juce_audio_formats\format diff --git a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj index 9ad0c94d94..7983b3c5aa 100644 --- a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj +++ b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj @@ -3139,6 +3139,7 @@ + diff --git a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj.filters b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj.filters index 9cb74f3ea3..2aeb535a06 100644 --- a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj.filters +++ b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_DynamicLibrary.vcxproj.filters @@ -4344,6 +4344,9 @@ JUCE Modules\juce_audio_formats\format + + JUCE Modules\juce_audio_formats\format + JUCE Modules\juce_audio_formats\format diff --git a/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp b/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp index 71e07ca871..1b5b9ece0b 100644 --- a/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp +++ b/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp @@ -1031,4 +1031,20 @@ AudioFormatWriter* AiffAudioFormat::createWriterFor (OutputStream* out, return nullptr; } +std::unique_ptr AiffAudioFormat::createWriterFor (std::unique_ptr& streamToWriteTo, + const AudioFormatWriterOptions& options) +{ + if (streamToWriteTo == nullptr || ! getPossibleBitDepths().contains (options.getBitsPerSample())) + return nullptr; + + StringPairArray metadata; + metadata.addUnorderedMap (options.getMetadataValues()); + + return std::make_unique (std::exchange (streamToWriteTo, {}).release(), + options.getSampleRate(), + (unsigned int) options.getNumChannels(), + (unsigned int) options.getBitsPerSample(), + metadata); +} + } // namespace juce diff --git a/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.h b/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.h index b17cd67569..0830638581 100644 --- a/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.h +++ b/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.h @@ -94,6 +94,10 @@ public: int bitsPerSample, const StringPairArray& metadataValues, int qualityOptionIndex) override; + + std::unique_ptr createWriterFor (std::unique_ptr& streamToWriteTo, + const AudioFormatWriterOptions& options) override; + using AudioFormat::createWriterFor; private: diff --git a/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp b/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp index 73151b374b..8c0e429968 100644 --- a/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp +++ b/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp @@ -665,6 +665,13 @@ AudioFormatWriter* CoreAudioFormat::createWriterFor (OutputStream*, return nullptr; } +std::unique_ptr CoreAudioFormat::createWriterFor (std::unique_ptr&, + const AudioFormatWriterOptions&) +{ + jassertfalse; // not yet implemented! + return nullptr; +} + //============================================================================== //============================================================================== diff --git a/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.h b/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.h index 72f1896169..16c284a078 100644 --- a/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.h +++ b/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.h @@ -112,6 +112,10 @@ public: int bitsPerSample, const StringPairArray& metadataValues, int qualityOptionIndex) override; + + std::unique_ptr createWriterFor (std::unique_ptr& streamToWriteTo, + const AudioFormatWriterOptions& options) override; + using AudioFormat::createWriterFor; private: diff --git a/modules/juce_audio_formats/codecs/juce_FlacAudioFormat.cpp b/modules/juce_audio_formats/codecs/juce_FlacAudioFormat.cpp index 851aea9466..2a886a1d53 100644 --- a/modules/juce_audio_formats/codecs/juce_FlacAudioFormat.cpp +++ b/modules/juce_audio_formats/codecs/juce_FlacAudioFormat.cpp @@ -610,7 +610,7 @@ AudioFormatWriter* FlacAudioFormat::createWriterFor (OutputStream* out, if (out != nullptr && getPossibleBitDepths().contains (bitsPerSample)) { std::unique_ptr w (new FlacWriter (out, sampleRate, numberOfChannels, - (uint32) bitsPerSample, qualityOptionIndex)); + (uint32) bitsPerSample, qualityOptionIndex)); if (w->ok) return w.release(); } @@ -618,6 +618,24 @@ AudioFormatWriter* FlacAudioFormat::createWriterFor (OutputStream* out, return nullptr; } +std::unique_ptr FlacAudioFormat::createWriterFor (std::unique_ptr& streamToWriteTo, + const AudioFormatWriterOptions& options) +{ + if (streamToWriteTo == nullptr || ! getPossibleBitDepths().contains (options.getBitsPerSample())) + return nullptr; + + auto writer = std::make_unique (std::exchange (streamToWriteTo, {}).release(), + options.getSampleRate(), + (uint32) options.getNumChannels(), + (uint32) options.getBitsPerSample(), + options.getQualityOptionIndex()); + + if (! writer->ok) + return nullptr; + + return writer; +} + StringArray FlacAudioFormat::getQualityOptions() { return { "0 (Fastest)", "1", "2", "3", "4", "5 (Default)","6", "7", "8 (Highest quality)" }; diff --git a/modules/juce_audio_formats/codecs/juce_FlacAudioFormat.h b/modules/juce_audio_formats/codecs/juce_FlacAudioFormat.h index 0a555b6ae6..99f88d7118 100644 --- a/modules/juce_audio_formats/codecs/juce_FlacAudioFormat.h +++ b/modules/juce_audio_formats/codecs/juce_FlacAudioFormat.h @@ -63,6 +63,9 @@ public: StringArray getQualityOptions() override; //============================================================================== + std::unique_ptr createWriterFor (std::unique_ptr& streamToWriteTo, + const AudioFormatWriterOptions& options) override; + AudioFormatReader* createReaderFor (InputStream* sourceStream, bool deleteStreamIfOpeningFails) override; @@ -72,6 +75,7 @@ public: int bitsPerSample, const StringPairArray& metadataValues, int qualityOptionIndex) override; + using AudioFormat::createWriterFor; private: diff --git a/modules/juce_audio_formats/codecs/juce_LAMEEncoderAudioFormat.cpp b/modules/juce_audio_formats/codecs/juce_LAMEEncoderAudioFormat.cpp index 06b8440201..0983ae0149 100644 --- a/modules/juce_audio_formats/codecs/juce_LAMEEncoderAudioFormat.cpp +++ b/modules/juce_audio_formats/codecs/juce_LAMEEncoderAudioFormat.cpp @@ -50,36 +50,39 @@ public: { WavAudioFormat wavFormat; - if (auto out = tempWav.getFile().createOutputStream()) + writer = wavFormat.createWriterFor (tempWav.getFile().createOutputStream(), + AudioFormatWriter::Options{}.withSampleRate (sampleRateIn) + .withNumChannels ((int) numberOfChannels) + .withBitsPerSample (bitsPerSampleIn) + .withMetadataValues (metadata)); + + if (writer == nullptr) + return; + + args.add (appFile.getFullPathName()); + + args.add ("--quiet"); + + if (cbrBitrate == 0) { - writer.reset (wavFormat.createWriterFor (out.release(), sampleRateIn, numChannels, - bitsPerSampleIn, metadata, 0)); - - args.add (appFile.getFullPathName()); - - args.add ("--quiet"); - - if (cbrBitrate == 0) - { - args.add ("--vbr-new"); - args.add ("-V"); - args.add (String (vbrLevel)); - } - else - { - args.add ("--cbr"); - args.add ("-b"); - args.add (String (cbrBitrate)); - } - - addMetadataArg (metadata, "id3title", "--tt"); - addMetadataArg (metadata, "id3artist", "--ta"); - addMetadataArg (metadata, "id3album", "--tl"); - addMetadataArg (metadata, "id3comment", "--tc"); - addMetadataArg (metadata, "id3date", "--ty"); - addMetadataArg (metadata, "id3genre", "--tg"); - addMetadataArg (metadata, "id3trackNumber", "--tn"); + args.add ("--vbr-new"); + args.add ("-V"); + args.add (String (vbrLevel)); } + else + { + args.add ("--cbr"); + args.add ("-b"); + args.add (String (cbrBitrate)); + } + + addMetadataArg (metadata, "id3title", "--tt"); + addMetadataArg (metadata, "id3artist", "--ta"); + addMetadataArg (metadata, "id3album", "--tl"); + addMetadataArg (metadata, "id3comment", "--tc"); + addMetadataArg (metadata, "id3date", "--ty"); + addMetadataArg (metadata, "id3genre", "--tg"); + addMetadataArg (metadata, "id3trackNumber", "--tn"); } void addMetadataArg (const StringPairArray& metadata, const char* key, const char* lameFlag) @@ -165,10 +168,6 @@ LAMEEncoderAudioFormat::LAMEEncoderAudioFormat (const File& lameApplication) { } -LAMEEncoderAudioFormat::~LAMEEncoderAudioFormat() -{ -} - bool LAMEEncoderAudioFormat::canHandleFile (const File&) { return false; @@ -208,6 +207,33 @@ AudioFormatReader* LAMEEncoderAudioFormat::createReaderFor (InputStream*, const return nullptr; } +std::unique_ptr LAMEEncoderAudioFormat::createWriterFor (std::unique_ptr streamToWriteTo, + const AudioFormatWriterOptions& options) +{ + if (streamToWriteTo == nullptr) + return nullptr; + + int vbr = 4; + int cbr = 0; + + const String qual (getQualityOptions() [options.getQualityOptionIndex()]); + + if (qual.contains ("VBR")) + vbr = qual.retainCharacters ("0123456789").getIntValue(); + else + cbr = qual.getIntValue(); + + return std::make_unique (streamToWriteTo.release(), + getFormatName(), + lameApp, + vbr, + cbr, + options.getSampleRate(), + (unsigned int) options.getNumChannels(), + options.getBitsPerSample(), + options.getMetadataValues()); +} + AudioFormatWriter* LAMEEncoderAudioFormat::createWriterFor (OutputStream* streamToWriteTo, double sampleRateToUse, unsigned int numberOfChannels, diff --git a/modules/juce_audio_formats/codecs/juce_LAMEEncoderAudioFormat.h b/modules/juce_audio_formats/codecs/juce_LAMEEncoderAudioFormat.h index a56925b877..50161131f1 100644 --- a/modules/juce_audio_formats/codecs/juce_LAMEEncoderAudioFormat.h +++ b/modules/juce_audio_formats/codecs/juce_LAMEEncoderAudioFormat.h @@ -60,21 +60,24 @@ public: executable at the location given. */ LAMEEncoderAudioFormat (const File& lameExecutableToUse); - ~LAMEEncoderAudioFormat(); - bool canHandleFile (const File&); - Array getPossibleSampleRates(); - Array getPossibleBitDepths(); - bool canDoStereo(); - bool canDoMono(); - bool isCompressed(); - StringArray getQualityOptions(); + bool canHandleFile (const File&) override; + Array getPossibleSampleRates() override; + Array getPossibleBitDepths() override; + bool canDoStereo() override; + bool canDoMono() override; + bool isCompressed() override; + StringArray getQualityOptions() override; - AudioFormatReader* createReaderFor (InputStream*, bool deleteStreamIfOpeningFails); + std::unique_ptr createWriterFor (std::unique_ptr streamToWriteTo, + const AudioFormatWriterOptions& options) override; + + AudioFormatReader* createReaderFor (InputStream*, bool deleteStreamIfOpeningFails) override; AudioFormatWriter* createWriterFor (OutputStream*, double sampleRateToUse, unsigned int numberOfChannels, int bitsPerSample, const StringPairArray& metadataValues, int qualityOptionIndex); + using AudioFormat::createWriterFor; private: diff --git a/modules/juce_audio_formats/codecs/juce_MP3AudioFormat.cpp b/modules/juce_audio_formats/codecs/juce_MP3AudioFormat.cpp index 9506b56e5a..dd91fe632c 100644 --- a/modules/juce_audio_formats/codecs/juce_MP3AudioFormat.cpp +++ b/modules/juce_audio_formats/codecs/juce_MP3AudioFormat.cpp @@ -3173,6 +3173,13 @@ AudioFormatReader* MP3AudioFormat::createReaderFor (InputStream* sourceStream, c return nullptr; } +std::unique_ptr MP3AudioFormat::createWriterFor (std::unique_ptr&, + const AudioFormatWriterOptions&) +{ + jassertfalse; // not yet implemented! + return nullptr; +} + AudioFormatWriter* MP3AudioFormat::createWriterFor (OutputStream*, double /*sampleRateToUse*/, unsigned int /*numberOfChannels*/, int /*bitsPerSample*/, const StringPairArray& /*metadataValues*/, int /*qualityOptionIndex*/) diff --git a/modules/juce_audio_formats/codecs/juce_MP3AudioFormat.h b/modules/juce_audio_formats/codecs/juce_MP3AudioFormat.h index 0256b8bab7..e3e1c67d01 100644 --- a/modules/juce_audio_formats/codecs/juce_MP3AudioFormat.h +++ b/modules/juce_audio_formats/codecs/juce_MP3AudioFormat.h @@ -74,6 +74,10 @@ public: AudioFormatWriter* createWriterFor (OutputStream*, double sampleRateToUse, unsigned int numberOfChannels, int bitsPerSample, const StringPairArray& metadataValues, int qualityOptionIndex) override; + + std::unique_ptr createWriterFor (std::unique_ptr& streamToWriteTo, + const AudioFormatWriterOptions& options) override; + using AudioFormat::createWriterFor; }; diff --git a/modules/juce_audio_formats/codecs/juce_OggVorbisAudioFormat.cpp b/modules/juce_audio_formats/codecs/juce_OggVorbisAudioFormat.cpp index 15f744ac16..4c5b0c99ff 100644 --- a/modules/juce_audio_formats/codecs/juce_OggVorbisAudioFormat.cpp +++ b/modules/juce_audio_formats/codecs/juce_OggVorbisAudioFormat.cpp @@ -463,6 +463,28 @@ AudioFormatReader* OggVorbisAudioFormat::createReaderFor (InputStream* in, bool return nullptr; } +std::unique_ptr OggVorbisAudioFormat::createWriterFor (std::unique_ptr& streamToWriteTo, + const AudioFormatWriterOptions& options) +{ + if (streamToWriteTo == nullptr) + return nullptr; + + StringPairArray metadata; + metadata.addUnorderedMap (options.getMetadataValues()); + + auto w = std::make_unique (std::exchange (streamToWriteTo, {}).release(), + options.getSampleRate(), + (unsigned int) options.getNumChannels(), + (unsigned int) options.getBitsPerSample(), + options.getQualityOptionIndex(), + metadata); + + if (! w->ok) + return nullptr; + + return w; +} + AudioFormatWriter* OggVorbisAudioFormat::createWriterFor (OutputStream* out, double sampleRate, unsigned int numChannels, diff --git a/modules/juce_audio_formats/codecs/juce_OggVorbisAudioFormat.h b/modules/juce_audio_formats/codecs/juce_OggVorbisAudioFormat.h index 3cc49a8556..6b404823bb 100644 --- a/modules/juce_audio_formats/codecs/juce_OggVorbisAudioFormat.h +++ b/modules/juce_audio_formats/codecs/juce_OggVorbisAudioFormat.h @@ -93,12 +93,16 @@ public: AudioFormatReader* createReaderFor (InputStream* sourceStream, bool deleteStreamIfOpeningFails) override; + std::unique_ptr createWriterFor (std::unique_ptr& streamToWriteTo, + const AudioFormatWriterOptions& options) override; + AudioFormatWriter* createWriterFor (OutputStream* streamToWriteTo, double sampleRateToUse, unsigned int numberOfChannels, int bitsPerSample, const StringPairArray& metadataValues, int qualityOptionIndex) override; + using AudioFormat::createWriterFor; private: diff --git a/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp b/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp index a9a0ecb476..11a420fd5c 100644 --- a/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp +++ b/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp @@ -35,18 +35,6 @@ namespace juce { -using StringMap = std::unordered_map; - -static auto toMap (const StringPairArray& array) -{ - StringMap result; - - for (auto i = 0; i < array.size(); ++i) - result[array.getAllKeys()[i]] = array.getAllValues()[i]; - - return result; -} - static auto getValueWithDefault (const StringMap& m, const String& key, const String& fallback = {}) { const auto iter = m.find (key); @@ -1599,30 +1587,36 @@ class WavAudioFormatWriter final : public AudioFormatWriter public: WavAudioFormatWriter (OutputStream* const out, const double rate, const AudioChannelSet& channelLayoutToUse, const unsigned int bits, - const StringPairArray& metadataValues) + const StringMap& metadataValues, + AudioFormatWriterOptions::SampleFormat sampleFormat) : AudioFormatWriter (out, wavFormatName, rate, channelLayoutToUse, bits) { using namespace WavFileHelpers; + using SampleFormat = AudioFormatWriterOptions::SampleFormat; - if (metadataValues.size() > 0) + // The floating point format is only supported with a bit depth of 32 + jassert (sampleFormat != SampleFormat::floatingPoint || bits == 32); + + usesFloatingPointData = bits == 32 && sampleFormat != SampleFormat::integral; + + if (! metadataValues.empty()) { // The meta data should have been sanitised for the WAV format. // If it was originally sourced from an AIFF file the MetaDataSource // key should be removed (or set to "WAV") once this has been done - jassert (metadataValues.getValue ("MetaDataSource", "None") != "AIFF"); + jassert (metadataValues.count ("MetaDataSource") == 0 + || metadataValues.at ("MetaDataSource") != "AIFF"); - 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); - cueChunk = CueChunk ::createFrom (map); - listChunk = ListChunk::createFrom (map); - listInfoChunk = ListInfoChunk::createFrom (map); - acidChunk = AcidChunk::createFrom (map); - trckChunk = TracktionChunk::createFrom (map); + bwavChunk = BWAVChunk::createFrom (metadataValues); + ixmlChunk = IXMLChunk::createFrom (metadataValues); + axmlChunk = AXMLChunk::createFrom (metadataValues); + smplChunk = SMPLChunk::createFrom (metadataValues); + instChunk = InstChunk::createFrom (metadataValues); + cueChunk = CueChunk ::createFrom (metadataValues); + listChunk = ListChunk::createFrom (metadataValues); + listInfoChunk = ListInfoChunk::createFrom (metadataValues); + acidChunk = AcidChunk::createFrom (metadataValues); + trckChunk = TracktionChunk::createFrom (metadataValues); } headerPosition = out->getPosition(); @@ -1773,8 +1767,12 @@ private: else { writeChunkHeader (chunkName ("fmt "), 16); - output->writeShort (bitsPerSample < 32 ? (short) 1 /*WAVE_FORMAT_PCM*/ - : (short) 3 /*WAVE_FORMAT_IEEE_FLOAT*/); + + constexpr short waveFormatIeeeFloat = 3; + constexpr short waveFormatPcm = 1; + + output->writeShort (usesFloatingPointData ? waveFormatIeeeFloat + : waveFormatPcm); } output->writeShort ((short) numChannels); @@ -1789,7 +1787,7 @@ private: output->writeShort ((short) bitsPerSample); // wValidBitsPerSample output->writeInt (channelMask); - const ExtensibleWavSubFormat& subFormat = bitsPerSample < 32 ? pcmFormat : IEEEFloatFormat; + const ExtensibleWavSubFormat& subFormat = usesFloatingPointData ? IEEEFloatFormat : pcmFormat; output->writeInt ((int) subFormat.data1); output->writeShort ((short) subFormat.data2); @@ -1809,8 +1807,6 @@ private: writeChunk (trckChunk, chunkName ("Trkn")); writeChunkHeader (chunkName ("data"), isRF64 ? -1 : (int) (lengthInSamples * bytesPerFrame)); - - usesFloatingPointData = (bitsPerSample == 32); } static size_t chunkSize (const MemoryBlock& data) noexcept { return data.isEmpty() ? 0 : (8 + data.getSize()); } @@ -2027,6 +2023,25 @@ MemoryMappedAudioFormatReader* WavAudioFormat::createMemoryMappedReader (FileInp return nullptr; } +std::unique_ptr WavAudioFormat::createWriterFor (std::unique_ptr& streamToWriteTo, + const AudioFormatWriterOptions& options) +{ + if (streamToWriteTo == nullptr || ! getPossibleBitDepths().contains (options.getBitsPerSample())) + return nullptr; + + const auto layout = options.getChannelLayout().value_or (WavFileHelpers::canonicalWavChannelSet (options.getNumChannels())); + + if (! isChannelLayoutSupported (layout)) + return nullptr; + + return std::make_unique (std::exchange (streamToWriteTo, {}).release(), + options.getSampleRate(), + layout, + (unsigned int) options.getBitsPerSample(), + options.getMetadataValues(), + options.getSampleFormat()); +} + AudioFormatWriter* WavAudioFormat::createWriterFor (OutputStream* out, double sampleRate, unsigned int numChannels, int bitsPerSample, const StringPairArray& metadataValues, int qualityOptionIndex) @@ -2044,44 +2059,39 @@ AudioFormatWriter* WavAudioFormat::createWriterFor (OutputStream* out, { if (out != nullptr && getPossibleBitDepths().contains (bitsPerSample) && isChannelLayoutSupported (channelLayout)) return new WavAudioFormatWriter (out, sampleRate, channelLayout, - (unsigned int) bitsPerSample, metadataValues); + (unsigned int) bitsPerSample, toMap (metadataValues), + AudioFormatWriterOptions::SampleFormat::automatic); return nullptr; } namespace WavFileHelpers { - static bool slowCopyWavFileWithNewMetadata (const File& file, const StringPairArray& metadata) + static bool slowCopyWavFileWithNewMetadata (const File& file, const StringMap& metadata) { TemporaryFile tempFile (file); WavAudioFormat wav; std::unique_ptr reader (wav.createReaderFor (file.createInputStream().release(), true)); - if (reader != nullptr) - { - std::unique_ptr outStream (tempFile.getFile().createOutputStream()); + if (reader == nullptr) + return false; - if (outStream != nullptr) - { - std::unique_ptr writer (wav.createWriterFor (outStream.get(), reader->sampleRate, - reader->numChannels, (int) reader->bitsPerSample, - metadata, 0)); + std::unique_ptr stream = tempFile.getFile().createOutputStream(); + auto writer = wav.createWriterFor (stream, + AudioFormatWriter::Options{}.withSampleRate (reader->sampleRate) + .withNumChannels ((int) reader->numChannels) + .withBitsPerSample ((int) reader->bitsPerSample) + .withMetadataValues (metadata)); - if (writer != nullptr) - { - outStream.release(); + if (writer == nullptr) + return false; - bool ok = writer->writeFromAudioReader (*reader, 0, -1); - writer.reset(); - reader.reset(); + bool ok = writer->writeFromAudioReader (*reader, 0, -1); + writer.reset(); + reader.reset(); - return ok && tempFile.overwriteTargetFileWithTemporary(); - } - } - } - - return false; + return ok && tempFile.overwriteTargetFileWithTemporary(); } } @@ -2123,7 +2133,7 @@ bool WavAudioFormat::replaceMetadataInFile (const File& wavFile, const StringPai } } - return slowCopyWavFileWithNewMetadata (wavFile, newMetadata); + return slowCopyWavFileWithNewMetadata (wavFile, toMap (newMetadata)); } @@ -2172,15 +2182,14 @@ struct WaveAudioFormatTests final : public UnitTest { beginTest ("Metadata can be written and read"); - const auto newMetadata = getMetadataAfterReading (format, writeToBlock (format, metadataArray)); + const auto newMetadata = getMetadataAfterReading (format, writeToBlock (format, metadataValues)); expect (newMetadata == metadataArray, "Somehow, the metadata is different!"); } { 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, "" } }); + StringMap meta { { WavAudioFormat::riffInfoSource, "customsource" }, + { WavAudioFormat::internationalStandardRecordingCode, "" } }; const auto mb = writeToBlock (format, meta); checkPatternsPresent (mb, { "INFOISRC" }); checkPatternsNotPresent (mb, { "ISRC:", ""); + meta[key] = "Test123&<>"; { - auto writer = rawToUniquePtr (WavAudioFormat().createWriterFor (new MemoryOutputStream (block, false), 48000, 1, 32, meta, 0)); + const auto writerOptions = AudioFormatWriterOptions{}.withSampleRate (48000.0) + .withNumChannels (1) + .withBitsPerSample (32) + .withMetadataValues (meta); + + std::unique_ptr stream = std::make_unique (block, false); + auto writer = format.createWriterFor (stream, writerOptions); expect (writer != nullptr); } @@ -2270,9 +2282,8 @@ struct WaveAudioFormatTests final : public UnitTest auto reader = rawToUniquePtr (WavAudioFormat().createReaderFor (new MemoryInputStream (block, false), true)); expect (reader != nullptr); - for (const auto& key : meta.getAllKeys()) + for (const auto& [key, oldValue] : meta) { - const auto oldValue = meta.getValue (key, "!"); const auto newValue = reader->metadataValues.getValue (key, ""); expectEquals (oldValue, newValue); } @@ -2280,22 +2291,128 @@ struct WaveAudioFormatTests final : public UnitTest expect (reader->metadataValues.getValue (WavAudioFormat::aswgVersion, "") == "3.01"); } } + + { + beginTest ("Writing 32-bit integer samples should work"); + + const auto minimum = std::numeric_limits::min(); + const auto maximum = std::numeric_limits::max(); + + std::vector dataIn { minimum, + minimum + 1, + minimum + 999, + 0, + 1, + maximum - 1001, + maximum - 1, + maximum }; + + const int* ptr = dataIn.data(); + + WavFormatWriterTestData integralTestData { &ptr, + dataIn.size(), + AudioFormatWriterOptions::SampleFormat::integral }; + + auto* reader = integralTestData.getReader(); + + if (reader == nullptr) + { + expect (false, "WavFormatReader should be non-null"); + return; + } + + std::vector dataOut ((size_t) reader->lengthInSamples); + int* const outPtr = dataOut.data(); + reader->read (&outPtr, 1, 0, (int) dataOut.size(), false); + + expect (reader->usesFloatingPointData == false); + expect (dataIn == dataOut); + } + + { + beginTest ("Writing 32-bit float samples should work"); + + std::vector dataIn { -1.0f, + -0.8f, + 0.0f, + 0.8f, + 1.0f }; + + const int* ptr = (int*) dataIn.data(); + + WavFormatWriterTestData floatingPointTestData { &ptr, + dataIn.size(), + AudioFormatWriterOptions::SampleFormat::floatingPoint }; + + auto* reader = floatingPointTestData.getReader(); + + if (reader == nullptr) + { + expect (false, "WavFormatReader should be non-null"); + return; + } + + std::vector dataOut ((size_t) reader->lengthInSamples); + float* const outPtr = dataOut.data(); + reader->read (&outPtr, 1, 0, (int) dataOut.size()); + + expect (reader->usesFloatingPointData == true); + expect (dataIn.size() == dataOut.size()); + + for (auto [index, value] : enumerate (dataOut, size_t{})) + expect (approximatelyEqual (value, dataIn[index])); + } } private: - MemoryBlock writeToBlock (WavAudioFormat& format, StringPairArray meta) + struct WavFormatWriterTestData + { + WavFormatWriterTestData (const int** ptr, + size_t numSamples, + AudioFormatWriterOptions::SampleFormat sampleFormat) + { + const auto writerOptions = AudioFormatWriterOptions{}.withSampleRate (48000.0) + .withNumChannels (1) + .withBitsPerSample (32) + .withSampleFormat (sampleFormat); + + WavAudioFormat format; + + { + std::unique_ptr stream = std::make_unique (block, false); + auto writer = format.createWriterFor (stream, writerOptions); + + writer->write (ptr, (int) numSamples); + } + + reader = rawToUniquePtr (format.createReaderFor (new MemoryInputStream (block, false), true)); + } + + AudioFormatReader* getReader() + { + return reader.get(); + } + + private: + MemoryBlock block; + std::unique_ptr reader; + }; + + MemoryBlock writeToBlock (WavAudioFormat& format, const StringMap& meta) { MemoryBlock mb; { + const auto writerOptions = AudioFormatWriterOptions{}.withSampleRate (44100.0) + .withNumChannels ((int) numTestAudioBufferChannels) + .withBitsPerSample (16) + .withMetadataValues (meta); + // The destructor of the writer will modify the block, so make sure that we've // destroyed the writer before returning the block! - auto writer = rawToUniquePtr (format.createWriterFor (new MemoryOutputStream (mb, false), - 44100.0, - numTestAudioBufferChannels, - 16, - meta, - 0)); + std::unique_ptr stream = std::make_unique (mb, false); + auto writer = format.createWriterFor (stream, writerOptions); + expect (writer != nullptr); AudioBuffer buffer (numTestAudioBufferChannels, numTestAudioBufferSamples); expect (writer->writeFromAudioSampleBuffer (buffer, 0, numTestAudioBufferSamples)); diff --git a/modules/juce_audio_formats/codecs/juce_WavAudioFormat.h b/modules/juce_audio_formats/codecs/juce_WavAudioFormat.h index c55556b62f..9c9164bee6 100644 --- a/modules/juce_audio_formats/codecs/juce_WavAudioFormat.h +++ b/modules/juce_audio_formats/codecs/juce_WavAudioFormat.h @@ -297,6 +297,9 @@ public: MemoryMappedAudioFormatReader* createMemoryMappedReader (const File&) override; MemoryMappedAudioFormatReader* createMemoryMappedReader (FileInputStream*) override; + std::unique_ptr createWriterFor (std::unique_ptr& streamToWriteTo, + const AudioFormatWriterOptions& options) override; + AudioFormatWriter* createWriterFor (OutputStream* streamToWriteTo, double sampleRateToUse, unsigned int numberOfChannels, @@ -310,6 +313,7 @@ public: int bitsPerSample, const StringPairArray& metadataValues, int qualityOptionIndex) override; + using AudioFormat::createWriterFor; //============================================================================== diff --git a/modules/juce_audio_formats/codecs/juce_WindowsMediaAudioFormat.cpp b/modules/juce_audio_formats/codecs/juce_WindowsMediaAudioFormat.cpp index b0f34de302..1db0977d42 100644 --- a/modules/juce_audio_formats/codecs/juce_WindowsMediaAudioFormat.cpp +++ b/modules/juce_audio_formats/codecs/juce_WindowsMediaAudioFormat.cpp @@ -365,6 +365,13 @@ AudioFormatWriter* WindowsMediaAudioFormat::createWriterFor (OutputStream* /*str return nullptr; } +std::unique_ptr WindowsMediaAudioFormat::createWriterFor (std::unique_ptr&, + const AudioFormatWriterOptions&) +{ + jassertfalse; // not yet implemented! + return nullptr; +} + //============================================================================== //============================================================================== #if JUCE_UNIT_TESTS diff --git a/modules/juce_audio_formats/codecs/juce_WindowsMediaAudioFormat.h b/modules/juce_audio_formats/codecs/juce_WindowsMediaAudioFormat.h index d9f54372bf..7205eb82f5 100644 --- a/modules/juce_audio_formats/codecs/juce_WindowsMediaAudioFormat.h +++ b/modules/juce_audio_formats/codecs/juce_WindowsMediaAudioFormat.h @@ -60,9 +60,13 @@ public: //============================================================================== AudioFormatReader* createReaderFor (InputStream*, bool deleteStreamIfOpeningFails) override; + std::unique_ptr createWriterFor (std::unique_ptr& streamToWriteTo, + const AudioFormatWriterOptions& options) override; + AudioFormatWriter* createWriterFor (OutputStream*, double sampleRateToUse, unsigned int numberOfChannels, int bitsPerSample, const StringPairArray& metadataValues, int qualityOptionIndex) override; + using AudioFormat::createWriterFor; }; diff --git a/modules/juce_audio_formats/format/juce_AudioFormat.cpp b/modules/juce_audio_formats/format/juce_AudioFormat.cpp index 29ae945ed2..1ba965e1d8 100644 --- a/modules/juce_audio_formats/format/juce_AudioFormat.cpp +++ b/modules/juce_audio_formats/format/juce_AudioFormat.cpp @@ -82,6 +82,53 @@ bool AudioFormat::isChannelLayoutSupported (const AudioChannelSet& channelSet) return false; } +using StringMap = std::unordered_map; + +static StringMap toMap (const StringPairArray& array) +{ + StringMap result; + + for (auto i = 0; i < array.size(); ++i) + result[array.getAllKeys()[i]] = array.getAllValues()[i]; + + return result; +} + +AudioFormatWriter* AudioFormat::createWriterForRawPtr (OutputStream* streamToWriteTo, + const AudioFormatWriterOptions& opt) +{ + auto owned = rawToUniquePtr (streamToWriteTo); + + if (auto writer = createWriterFor (owned, opt)) + { + // Creating the writer succeeded, so it's the writer's responsibility to eventually free + // the stream + jassert (owned == nullptr); + return writer.release(); + } + + // Creating the writer failed, so the stream should remain alive for re-use + jassert (owned != nullptr); + owned.release(); + + return {}; +} + +AudioFormatWriter* AudioFormat::createWriterFor (OutputStream* streamToWriteTo, + double sampleRateToUse, + unsigned int numberOfChannels, + int bitsPerSample, + const StringPairArray& metadataValues, + int qualityOptionIndex) +{ + auto opt = AudioFormatWriter::Options{}.withSampleRate (sampleRateToUse) + .withNumChannels ((int) numberOfChannels) + .withBitsPerSample (bitsPerSample) + .withMetadataValues (toMap (metadataValues)) + .withQualityOptionIndex (qualityOptionIndex); + return createWriterForRawPtr (streamToWriteTo, opt); +} + AudioFormatWriter* AudioFormat::createWriterFor (OutputStream* streamToWriteTo, double sampleRateToUse, const AudioChannelSet& channelLayout, @@ -89,12 +136,15 @@ AudioFormatWriter* AudioFormat::createWriterFor (OutputStream* streamToWriteTo, const StringPairArray& metadataValues, int qualityOptionIndex) { - if (isChannelLayoutSupported (channelLayout)) - return createWriterFor (streamToWriteTo, sampleRateToUse, - static_cast (channelLayout.size()), - bitsPerSample, metadataValues, qualityOptionIndex); + if (! isChannelLayoutSupported (channelLayout)) + return nullptr; - return nullptr; + auto opt = AudioFormatWriter::Options{}.withSampleRate (sampleRateToUse) + .withChannelLayout (channelLayout) + .withBitsPerSample (bitsPerSample) + .withMetadataValues (toMap (metadataValues)) + .withQualityOptionIndex (qualityOptionIndex); + return createWriterForRawPtr (streamToWriteTo, opt); } } // namespace juce diff --git a/modules/juce_audio_formats/format/juce_AudioFormat.h b/modules/juce_audio_formats/format/juce_AudioFormat.h index 347c7d5af7..d04f01d3b8 100644 --- a/modules/juce_audio_formats/format/juce_AudioFormat.h +++ b/modules/juce_audio_formats/format/juce_AudioFormat.h @@ -125,6 +125,26 @@ public: virtual MemoryMappedAudioFormatReader* createMemoryMappedReader (const File& file); virtual MemoryMappedAudioFormatReader* createMemoryMappedReader (FileInputStream* fin); + /** Tries to create an object that can write to a stream with this audio format. + + If the writer can't be created for some reason (e.g. the parameters passed in + here aren't suitable), this will return nullptr. + + @param streamToWriteTo a reference to a unique_ptr that owns the output stream. If creating + the writer succeeds, then ownership of the stream will be + transferred to the writer, and this argument will be set to nullptr. + If creating the writer fails, then streamToWriteTo will remain + unchanged, allowing it to be reused to create a writer of a + different format. + @param options options that specify details of the output file. If the audio format + does not support these settings, then this function may return + nullptr. + + @see AudioFormatWriterOptions + */ + virtual std::unique_ptr createWriterFor (std::unique_ptr& streamToWriteTo, + const AudioFormatWriterOptions& options) = 0; + /** Tries to create an object that can write to a stream with this audio format. The writer object that is returned can be used to write to the stream, and @@ -217,6 +237,8 @@ protected: AudioFormat (StringRef formatName, StringRef fileExtensions); private: + AudioFormatWriter* createWriterForRawPtr (OutputStream*, const AudioFormatWriterOptions&); + //============================================================================== String formatName; StringArray fileExtensions; diff --git a/modules/juce_audio_formats/format/juce_AudioFormatWriter.h b/modules/juce_audio_formats/format/juce_AudioFormatWriter.h index ae4a90d83b..65b49624a3 100644 --- a/modules/juce_audio_formats/format/juce_AudioFormatWriter.h +++ b/modules/juce_audio_formats/format/juce_AudioFormatWriter.h @@ -93,6 +93,8 @@ protected: unsigned int bitsPerSample); public: + using Options = AudioFormatWriterOptions; + /** Destructor. */ virtual ~AudioFormatWriter(); diff --git a/modules/juce_audio_formats/format/juce_AudioFormatWriterOptions.h b/modules/juce_audio_formats/format/juce_AudioFormatWriterOptions.h new file mode 100644 index 0000000000..c5b2712499 --- /dev/null +++ b/modules/juce_audio_formats/format/juce_AudioFormatWriterOptions.h @@ -0,0 +1,182 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +/** + Options that affect the output data format produced by an AudioFormatWriter. Format + specific writers may ignore some of these options. + + @see AudioFormat::createWriterFor(), AudioFormatWriter + + @tags{Audio} +*/ +class JUCE_API AudioFormatWriterOptions final +{ +public: + /** Used to provide a hint to the AudioFormatWriter for the output sample format. + + Use automatic for the old behaviour. The values integral and floatingPoint can be used with + the WavAudioFormat when using a bit depth of 32. Other formats are not affected by this + setting. + */ + enum class SampleFormat + { + automatic, ///< Lets the writer decide the format based on the other parameter values. + integral, ///< Integral format, e.g. PCM in case of the WavAudioFormat + floatingPoint ///< IEEE floating point format + }; + + /** Returns a copy of these options with the specified sample format. + + @see SampleFormat + */ + [[nodiscard]] AudioFormatWriterOptions withSampleFormat (SampleFormat x) const + { + return withMember (*this, &AudioFormatWriterOptions::sampleFormat, x); + } + + /** Returns a copy of these options with the specified sample rate. + + This specifies the sample rate for the file, which must be one of the ones + returned by AudioFormat::getPossibleSampleRates(). + */ + [[nodiscard]] AudioFormatWriterOptions withSampleRate (double x) const + { + return withMember (*this, &AudioFormatWriterOptions::sampleRate, x); + } + + /** Returns a copy of these options with the specified channel set. + + Setting this option will supersede the value passed into withNumChannels(). + + You should prefer to use withChannelLayout(), if specifying an AudioChannelSet is + applicable, and withNumChannels() otherwise. + */ + [[nodiscard]] AudioFormatWriterOptions withChannelLayout (const AudioChannelSet& x) const + { + return withMember (*this, &AudioFormatWriterOptions::channelLayout, x); + } + + /** Returns a copy of these options with the specified number of channels. + + This is meant as a fallback for specifying the channel layout. Setting this option will + have no effect if the channel layout is specified. + + @see withChannelLayout() + */ + [[nodiscard]] AudioFormatWriterOptions withNumChannels (int x) const + { + return withMember (*this, &AudioFormatWriterOptions::numChannels, x); + } + + /** Returns a copy of these options with the specified bit size per sample. + + This must be one of the values returned by AudioFormat::getPossibleBitDepths(). + */ + [[nodiscard]] AudioFormatWriterOptions withBitsPerSample (int x) const + { + return withMember (*this, &AudioFormatWriterOptions::bitsPerSample, x); + } + + /** Returns a copy of these options with the specified metadata container. + + As an alternative to this function, you can specify the key-value pairs one-by-one using + the withMetadata function. + + Subsequent calls of this function overwrites all previously added metadata. + + This parameter is a set of metadata values that the writer should try to write to the stream. + Exactly what these are depends on the format, and the subclass doesn't actually have to do + anything with them if it doesn't want to. Have a look at the specific format implementation + classes to see possible values that can be used. + */ + [[nodiscard]] AudioFormatWriterOptions withMetadataValues (const std::unordered_map& x) const + { + return withMember (*this, &AudioFormatWriterOptions::metadataValues, x); + } + + /** Returns a copy of these options with the specified metadata added. + + Subsequent calls of this function adds new metadata values, while also preserving the + previously added ones. + + Here you can specify metadata values that the writer should try to write to the stream. + Exactly what these are depends on the format, and the subclass doesn't actually have to do + anything with them if it doesn't want to. Have a look at the specific format implementation + classes to see possible values that can be used. + */ + [[nodiscard]] AudioFormatWriterOptions withMetadata (const String& key, const String& value) const + { + auto copy = *this; + copy.metadataValues[key] = value; + return copy; + } + + /** Returns a copy of these options with the specified quality option index. + + The index of one of the items returned by the AudioFormat::getQualityOptions() method. + */ + [[nodiscard]] AudioFormatWriterOptions withQualityOptionIndex (int x) const + { + return withMember (*this, &AudioFormatWriterOptions::qualityOptionIndex, x); + } + + /** @see withSampleRate() */ + [[nodiscard]] auto getSampleRate() const { return sampleRate; } + /** @see withChannelLayout() */ + [[nodiscard]] auto getChannelLayout() const { return channelLayout; } + /** @see withNumChannels() */ + [[nodiscard]] auto getNumChannels() const { return channelLayout.has_value() ? channelLayout->size() : numChannels; } + /** @see withBitsPerSample() */ + [[nodiscard]] auto getBitsPerSample() const { return bitsPerSample; } + /** @see withMetadataValues() */ + [[nodiscard]] auto getMetadataValues() const { return metadataValues; } + /** @see withQualityOptionIndex() */ + [[nodiscard]] auto getQualityOptionIndex() const { return qualityOptionIndex; } + /** @see withSampleFormat() */ + [[nodiscard]] auto getSampleFormat() const { return sampleFormat; } + +private: + double sampleRate = 48000.0; + int numChannels = 1; + std::optional channelLayout = std::nullopt; + int bitsPerSample = 16; + std::unordered_map metadataValues; + int qualityOptionIndex = 0; + SampleFormat sampleFormat = SampleFormat::automatic; +}; + +} // namespace juce diff --git a/modules/juce_audio_formats/juce_audio_formats.h b/modules/juce_audio_formats/juce_audio_formats.h index e8030e0cc3..52ed9a82b2 100644 --- a/modules/juce_audio_formats/juce_audio_formats.h +++ b/modules/juce_audio_formats/juce_audio_formats.h @@ -121,6 +121,7 @@ //============================================================================== #include "format/juce_AudioFormatReader.h" +#include "format/juce_AudioFormatWriterOptions.h" #include "format/juce_AudioFormatWriter.h" #include "format/juce_MemoryMappedAudioFormatReader.h" #include "format/juce_AudioFormat.h"