OpenShot Audio Library | OpenShotAudio: juce_FlacAudioFormat.cpp Source File

OpenShot Audio Library | OpenShotAudio 0.4.0

Loading...
Searching...
No Matches
juce_FlacAudioFormat.cpp
1/*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11 Agreement and JUCE Privacy Policy.
12
13 End User License Agreement: www.juce.com/juce-7-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
15
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
18
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
22
23 ==============================================================================
24*/
25
26namespace juce
27{
28
29#if JUCE_USE_FLAC
30
31}
32
33#if defined _WIN32 && !defined __CYGWIN__
34 #include <io.h>
35#else
36 #include <unistd.h>
37#endif
38
39#if defined _MSC_VER || defined __BORLANDC__ || defined __MINGW32__
40 #include <sys/types.h> /* for off_t */
41#endif
42
43#if HAVE_INTTYPES_H
44 #define __STDC_FORMAT_MACROS
45 #include <inttypes.h>
46#endif
47
48#if defined _MSC_VER || defined __MINGW32__ || defined __CYGWIN__ || defined __EMX__
49 #include <io.h> /* for _setmode(), chmod() */
50 #include <fcntl.h> /* for _O_BINARY */
51#else
52 #include <unistd.h> /* for chown(), unlink() */
53#endif
54
55#if defined _MSC_VER || defined __BORLANDC__ || defined __MINGW32__
56 #if defined __BORLANDC__
57 #include <utime.h> /* for utime() */
58 #else
59 #include <sys/utime.h> /* for utime() */
60 #endif
61#else
62 #include <sys/types.h> /* some flavors of BSD (like OS X) require this to get time_t */
63 #include <utime.h> /* for utime() */
64#endif
65
66#if defined _MSC_VER
67 #if _MSC_VER >= 1600
68 #include <stdint.h>
69 #else
70 #include <limits.h>
71 #endif
72#endif
73
74#ifdef _WIN32
75 #include <stdio.h>
76 #include <sys/stat.h>
77 #include <stdarg.h>
78 #include <windows.h>
79#endif
80
81#ifdef DEBUG
82 #include <assert.h>
83#endif
84
85#include <stdlib.h>
86#include <stdio.h>
87
88namespace juce
89{
90
91namespace FlacNamespace
92{
93#if JUCE_INCLUDE_FLAC_CODE || ! defined (JUCE_INCLUDE_FLAC_CODE)
94
95 #undef PACKAGE_VERSION
96 #define PACKAGE_VERSION "1.4.3"
97
98 #define FLAC__NO_DLL 1
99
100 JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4267 4127 4244 4996 4100 4701 4702 4013 4133 4206 4312 4505 4365 4005 4334 181 111 6340 6308 6297 6001 6320)
101 #if ! JUCE_MSVC
102 #define HAVE_LROUND 1
103 #endif
104
105 #if JUCE_MAC
106 #define FLAC__SYS_DARWIN 1
107 #endif
108
109 #ifndef SIZE_MAX
110 #define SIZE_MAX 0xffffffff
111 #endif
112
113 JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wconversion",
114 "-Wdeprecated-register",
115 "-Wfloat-equal",
116 "-Wimplicit-fallthrough",
117 "-Wlanguage-extension-token",
118 "-Wredundant-decls",
119 "-Wshadow",
120 "-Wsign-conversion",
121 "-Wswitch-default",
122 "-Wswitch-enum",
123 "-Wzero-as-null-pointer-constant")
124
125 #if JUCE_INTEL
126 #if JUCE_32BIT
127 #define FLAC__CPU_IA32 1
128 #endif
129 #if JUCE_64BIT
130 #define FLAC__CPU_X86_64 1
131 #endif
132 #define FLAC__HAS_X86INTRIN 1
133 #endif
134
135 #if JUCE_ARM && JUCE_64BIT
136 #define FLAC__CPU_ARM64 1
137
138 #if JUCE_USE_ARM_NEON
139 #define FLAC__HAS_NEONINTRIN 1
140 #define FLAC__HAS_A64NEONINTRIN 1
141 #endif
142 #endif
143
144 #define flac_max jmax
145 #define flac_min jmin
146
147 #pragma push_macro ("DEBUG")
148 #pragma push_macro ("NDEBUG")
149 #undef DEBUG // (some flac code dumps debug trace if the app defines this macro)
150
151 #ifndef NDEBUG
152 #define NDEBUG // (some flac code prints cpu info if this isn't defined)
153 #endif
154
155 #include "flac/all.h"
156 #include "flac/libFLAC/bitmath.c"
157 #include "flac/libFLAC/bitreader.c"
158 #include "flac/libFLAC/bitwriter.c"
159 #include "flac/libFLAC/cpu.c"
160 #include "flac/libFLAC/crc.c"
161 #include "flac/libFLAC/fixed.c"
162 #include "flac/libFLAC/float.c"
163 #include "flac/libFLAC/format.c"
164 #include "flac/libFLAC/lpc_flac.c"
165 #include "flac/libFLAC/lpc_intrin_neon.c"
166 #include "flac/libFLAC/md5.c"
167 #include "flac/libFLAC/memory.c"
168 #include "flac/libFLAC/stream_decoder.c"
169 #include "flac/libFLAC/stream_encoder.c"
170 #include "flac/libFLAC/stream_encoder_framing.c"
171 #include "flac/libFLAC/window_flac.c"
172
173 #pragma pop_macro ("DEBUG")
174 #pragma pop_macro ("NDEBUG")
175
176 #undef PACKAGE_VERSION
177
178 JUCE_END_IGNORE_WARNINGS_GCC_LIKE
179 JUCE_END_IGNORE_WARNINGS_MSVC
180
181#else
182 #include <FLAC/all.h>
183#endif
184}
185
186#undef max
187#undef min
188
189//==============================================================================
190static const char* const flacFormatName = "FLAC file";
191
192template <typename Item>
193auto emptyRange (Item item) { return Range<Item>::emptyRange (item); }
194
195//==============================================================================
196class FlacReader final : public AudioFormatReader
197{
198public:
199 FlacReader (InputStream* in) : AudioFormatReader (in, flacFormatName)
200 {
201 lengthInSamples = 0;
202 decoder = FlacNamespace::FLAC__stream_decoder_new();
203
204 ok = FLAC__stream_decoder_init_stream (decoder,
205 readCallback_, seekCallback_, tellCallback_, lengthCallback_,
206 eofCallback_, writeCallback_, metadataCallback_, errorCallback_,
207 this) == FlacNamespace::FLAC__STREAM_DECODER_INIT_STATUS_OK;
208
209 if (ok)
210 {
211 FLAC__stream_decoder_process_until_end_of_metadata (decoder);
212
213 if (lengthInSamples == 0 && sampleRate > 0)
214 {
215 // the length hasn't been stored in the metadata, so we'll need to
216 // work it out the length the hard way, by scanning the whole file..
217 scanningForLength = true;
218 FLAC__stream_decoder_process_until_end_of_stream (decoder);
219 scanningForLength = false;
220 auto tempLength = lengthInSamples;
221
222 FLAC__stream_decoder_reset (decoder);
223 FLAC__stream_decoder_process_until_end_of_metadata (decoder);
224 lengthInSamples = tempLength;
225 }
226 }
227 }
228
229 ~FlacReader() override
230 {
231 FlacNamespace::FLAC__stream_decoder_delete (decoder);
232 }
233
234 void useMetadata (const FlacNamespace::FLAC__StreamMetadata_StreamInfo& info)
235 {
236 sampleRate = info.sample_rate;
237 bitsPerSample = info.bits_per_sample;
238 lengthInSamples = (unsigned int) info.total_samples;
239 numChannels = info.channels;
240
241 reservoir.setSize ((int) numChannels, 2 * (int) info.max_blocksize, false, false, true);
242 }
243
244 bool readSamples (int* const* destSamples, int numDestChannels, int startOffsetInDestBuffer,
245 int64 startSampleInFile, int numSamples) override
246 {
247 if (! ok)
248 return false;
249
250 const auto getBufferedRange = [this] { return bufferedRange; };
251
252 const auto readFromReservoir = [this, &destSamples, &numDestChannels, &startOffsetInDestBuffer, &startSampleInFile] (const Range<int64> rangeToRead)
253 {
254 const auto bufferIndices = rangeToRead - bufferedRange.getStart();
255 const auto writePos = (int64) startOffsetInDestBuffer + (rangeToRead.getStart() - startSampleInFile);
256
257 for (int i = jmin (numDestChannels, reservoir.getNumChannels()); --i >= 0;)
258 {
259 if (destSamples[i] != nullptr)
260 {
261 memcpy (destSamples[i] + writePos,
262 reservoir.getReadPointer (i) + bufferIndices.getStart(),
263 (size_t) bufferIndices.getLength() * sizeof (int));
264 }
265 }
266 };
267
268 const auto fillReservoir = [this] (const int64 requestedStart)
269 {
270 if (requestedStart >= lengthInSamples)
271 {
272 bufferedRange = emptyRange (requestedStart);
273 return;
274 }
275
276 if (requestedStart < bufferedRange.getStart()
277 || jmax (bufferedRange.getEnd(), bufferedRange.getStart() + (int64) 511) < requestedStart)
278 {
279 // had some problems with flac crashing if the read pos is aligned more
280 // accurately than this. Probably fixed in newer versions of the library, though.
281 bufferedRange = emptyRange (requestedStart & ~511);
282 FLAC__stream_decoder_seek_absolute (decoder, (FlacNamespace::FLAC__uint64) bufferedRange.getStart());
283 return;
284 }
285
286 bufferedRange = emptyRange (bufferedRange.getEnd());
287 FLAC__stream_decoder_process_single (decoder);
288 };
289
290 const auto remainingSamples = Reservoir::doBufferedRead (Range<int64> { startSampleInFile, startSampleInFile + numSamples },
291 getBufferedRange,
292 readFromReservoir,
293 fillReservoir);
294
295 if (! remainingSamples.isEmpty())
296 for (int i = numDestChannels; --i >= 0;)
297 if (destSamples[i] != nullptr)
298 zeromem (destSamples[i] + startOffsetInDestBuffer + (remainingSamples.getStart() - startSampleInFile),
299 (size_t) remainingSamples.getLength() * sizeof (int));
300
301 return true;
302 }
303
304 void useSamples (const FlacNamespace::FLAC__int32* const buffer[], int numSamples)
305 {
306 if (scanningForLength)
307 {
308 lengthInSamples += numSamples;
309 }
310 else
311 {
312 if (numSamples > reservoir.getNumSamples())
313 reservoir.setSize ((int) numChannels, numSamples, false, false, true);
314
315 auto bitsToShift = 32 - bitsPerSample;
316
317 for (int i = 0; i < (int) numChannels; ++i)
318 {
319 auto* src = buffer[i];
320 int n = i;
321
322 while (src == nullptr && n > 0)
323 src = buffer [--n];
324
325 if (src != nullptr)
326 {
327 auto* dest = reinterpret_cast<int*> (reservoir.getWritePointer (i));
328
329 for (int j = 0; j < numSamples; ++j)
330 dest[j] = src[j] << bitsToShift;
331 }
332 }
333
334 bufferedRange.setLength (numSamples);
335 }
336 }
337
338 //==============================================================================
339 static FlacNamespace::FLAC__StreamDecoderReadStatus readCallback_ (const FlacNamespace::FLAC__StreamDecoder*, FlacNamespace::FLAC__byte buffer[], size_t* bytes, void* client_data)
340 {
341 *bytes = (size_t) static_cast<const FlacReader*> (client_data)->input->read (buffer, (int) *bytes);
342 return FlacNamespace::FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
343 }
344
345 static FlacNamespace::FLAC__StreamDecoderSeekStatus seekCallback_ (const FlacNamespace::FLAC__StreamDecoder*, FlacNamespace::FLAC__uint64 absolute_byte_offset, void* client_data)
346 {
347 static_cast<const FlacReader*> (client_data)->input->setPosition ((int) absolute_byte_offset);
348 return FlacNamespace::FLAC__STREAM_DECODER_SEEK_STATUS_OK;
349 }
350
351 static FlacNamespace::FLAC__StreamDecoderTellStatus tellCallback_ (const FlacNamespace::FLAC__StreamDecoder*, FlacNamespace::FLAC__uint64* absolute_byte_offset, void* client_data)
352 {
353 *absolute_byte_offset = (uint64) static_cast<const FlacReader*> (client_data)->input->getPosition();
354 return FlacNamespace::FLAC__STREAM_DECODER_TELL_STATUS_OK;
355 }
356
357 static FlacNamespace::FLAC__StreamDecoderLengthStatus lengthCallback_ (const FlacNamespace::FLAC__StreamDecoder*, FlacNamespace::FLAC__uint64* stream_length, void* client_data)
358 {
359 *stream_length = (uint64) static_cast<const FlacReader*> (client_data)->input->getTotalLength();
360 return FlacNamespace::FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
361 }
362
363 static FlacNamespace::FLAC__bool eofCallback_ (const FlacNamespace::FLAC__StreamDecoder*, void* client_data)
364 {
365 return static_cast<const FlacReader*> (client_data)->input->isExhausted();
366 }
367
368 static FlacNamespace::FLAC__StreamDecoderWriteStatus writeCallback_ (const FlacNamespace::FLAC__StreamDecoder*,
369 const FlacNamespace::FLAC__Frame* frame,
370 const FlacNamespace::FLAC__int32* const buffer[],
371 void* client_data)
372 {
373 static_cast<FlacReader*> (client_data)->useSamples (buffer, (int) frame->header.blocksize);
374 return FlacNamespace::FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
375 }
376
377 static void metadataCallback_ (const FlacNamespace::FLAC__StreamDecoder*,
378 const FlacNamespace::FLAC__StreamMetadata* metadata,
379 void* client_data)
380 {
381 static_cast<FlacReader*> (client_data)->useMetadata (metadata->data.stream_info);
382 }
383
384 static void errorCallback_ (const FlacNamespace::FLAC__StreamDecoder*, FlacNamespace::FLAC__StreamDecoderErrorStatus, void*)
385 {
386 }
387
388private:
389 FlacNamespace::FLAC__StreamDecoder* decoder;
390 AudioBuffer<float> reservoir;
391 Range<int64> bufferedRange;
392 bool ok = false, scanningForLength = false;
393
394 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlacReader)
395};
396
397
398//==============================================================================
399class FlacWriter final : public AudioFormatWriter
400{
401public:
402 FlacWriter (OutputStream* out, double rate, uint32 numChans, uint32 bits, int qualityOptionIndex)
403 : AudioFormatWriter (out, flacFormatName, rate, numChans, bits),
404 streamStartPos (output != nullptr ? jmax (output->getPosition(), 0ll) : 0ll)
405 {
406 encoder = FlacNamespace::FLAC__stream_encoder_new();
407
408 if (qualityOptionIndex > 0)
409 FLAC__stream_encoder_set_compression_level (encoder, (uint32) jmin (8, qualityOptionIndex));
410
411 FLAC__stream_encoder_set_do_mid_side_stereo (encoder, numChannels == 2);
412 FLAC__stream_encoder_set_loose_mid_side_stereo (encoder, numChannels == 2);
413 FLAC__stream_encoder_set_channels (encoder, numChannels);
414 FLAC__stream_encoder_set_bits_per_sample (encoder, jmin ((unsigned int) 24, bitsPerSample));
415 FLAC__stream_encoder_set_sample_rate (encoder, (unsigned int) sampleRate);
416 FLAC__stream_encoder_set_blocksize (encoder, 0);
417 FLAC__stream_encoder_set_do_escape_coding (encoder, true);
418
419 ok = FLAC__stream_encoder_init_stream (encoder,
420 encodeWriteCallback, encodeSeekCallback,
421 encodeTellCallback, encodeMetadataCallback,
422 this) == FlacNamespace::FLAC__STREAM_ENCODER_INIT_STATUS_OK;
423 }
424
425 ~FlacWriter() override
426 {
427 if (ok)
428 {
429 FlacNamespace::FLAC__stream_encoder_finish (encoder);
430 output->flush();
431 }
432 else
433 {
434 output = nullptr; // to stop the base class deleting this, as it needs to be returned
435 // to the caller of createWriter()
436 }
437
438 FlacNamespace::FLAC__stream_encoder_delete (encoder);
439 }
440
441 //==============================================================================
442 bool write (const int** samplesToWrite, int numSamples) override
443 {
444 if (! ok)
445 return false;
446
447 HeapBlock<int*> channels;
448 HeapBlock<int> temp;
449 auto bitsToShift = 32 - (int) bitsPerSample;
450
451 if (bitsToShift > 0)
452 {
453 temp.malloc (numChannels * (size_t) numSamples);
454 channels.calloc (numChannels + 1);
455
456 for (unsigned int i = 0; i < numChannels; ++i)
457 {
458 if (samplesToWrite[i] == nullptr)
459 break;
460
461 auto* destData = temp.get() + i * (size_t) numSamples;
462 channels[i] = destData;
463
464 for (int j = 0; j < numSamples; ++j)
465 destData[j] = (samplesToWrite[i][j] >> bitsToShift);
466 }
467
468 samplesToWrite = const_cast<const int**> (channels.get());
469 }
470
471 return FLAC__stream_encoder_process (encoder, (const FlacNamespace::FLAC__int32**) samplesToWrite, (unsigned) numSamples) != 0;
472 }
473
474 bool writeData (const void* const data, const int size) const
475 {
476 return output->write (data, (size_t) size);
477 }
478
479 static void packUint32 (FlacNamespace::FLAC__uint32 val, FlacNamespace::FLAC__byte* b, const int bytes)
480 {
481 b += bytes;
482
483 for (int i = 0; i < bytes; ++i)
484 {
485 *(--b) = (FlacNamespace::FLAC__byte) (val & 0xff);
486 val >>= 8;
487 }
488 }
489
490 void writeMetaData (const FlacNamespace::FLAC__StreamMetadata* metadata)
491 {
492 using namespace FlacNamespace;
493 auto& info = metadata->data.stream_info;
494
495 unsigned char buffer[FLAC__STREAM_METADATA_STREAMINFO_LENGTH];
496 const unsigned int channelsMinus1 = info.channels - 1;
497 const unsigned int bitsMinus1 = info.bits_per_sample - 1;
498
499 packUint32 (info.min_blocksize, buffer, 2);
500 packUint32 (info.max_blocksize, buffer + 2, 2);
501 packUint32 (info.min_framesize, buffer + 4, 3);
502 packUint32 (info.max_framesize, buffer + 7, 3);
503 buffer[10] = (uint8) ((info.sample_rate >> 12) & 0xff);
504 buffer[11] = (uint8) ((info.sample_rate >> 4) & 0xff);
505 buffer[12] = (uint8) (((info.sample_rate & 0x0f) << 4) | (channelsMinus1 << 1) | (bitsMinus1 >> 4));
506 buffer[13] = (FLAC__byte) (((bitsMinus1 & 0x0f) << 4) | (unsigned int) ((info.total_samples >> 32) & 0x0f));
507 packUint32 ((FLAC__uint32) info.total_samples, buffer + 14, 4);
508 memcpy (buffer + 18, info.md5sum, 16);
509
510 [[maybe_unused]] const bool seekOk = output->setPosition (streamStartPos + 4);
511
512 // if this fails, you've given it an output stream that can't seek! It needs
513 // to be able to seek back to write the header
514 jassert (seekOk);
515
516 output->writeIntBigEndian (FLAC__STREAM_METADATA_STREAMINFO_LENGTH);
517 output->write (buffer, FLAC__STREAM_METADATA_STREAMINFO_LENGTH);
518 }
519
520 //==============================================================================
521 static FlacNamespace::FLAC__StreamEncoderWriteStatus encodeWriteCallback (const FlacNamespace::FLAC__StreamEncoder*,
522 const FlacNamespace::FLAC__byte buffer[],
523 size_t bytes,
524 unsigned int /*samples*/,
525 unsigned int /*current_frame*/,
526 void* client_data)
527 {
528 return static_cast<FlacWriter*> (client_data)->writeData (buffer, (int) bytes)
529 ? FlacNamespace::FLAC__STREAM_ENCODER_WRITE_STATUS_OK
530 : FlacNamespace::FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR;
531 }
532
533 static FlacNamespace::FLAC__StreamEncoderSeekStatus encodeSeekCallback (const FlacNamespace::FLAC__StreamEncoder*, FlacNamespace::FLAC__uint64, void*)
534 {
535 return FlacNamespace::FLAC__STREAM_ENCODER_SEEK_STATUS_UNSUPPORTED;
536 }
537
538 static FlacNamespace::FLAC__StreamEncoderTellStatus encodeTellCallback (const FlacNamespace::FLAC__StreamEncoder*, FlacNamespace::FLAC__uint64* absolute_byte_offset, void* client_data)
539 {
540 if (client_data == nullptr)
541 return FlacNamespace::FLAC__STREAM_ENCODER_TELL_STATUS_UNSUPPORTED;
542
543 *absolute_byte_offset = (FlacNamespace::FLAC__uint64) static_cast<FlacWriter*> (client_data)->output->getPosition();
544 return FlacNamespace::FLAC__STREAM_ENCODER_TELL_STATUS_OK;
545 }
546
547 static void encodeMetadataCallback (const FlacNamespace::FLAC__StreamEncoder*, const FlacNamespace::FLAC__StreamMetadata* metadata, void* client_data)
548 {
549 static_cast<FlacWriter*> (client_data)->writeMetaData (metadata);
550 }
551
552 bool ok = false;
553
554private:
555 FlacNamespace::FLAC__StreamEncoder* encoder;
556 int64 streamStartPos;
557
558 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlacWriter)
559};
560
561
562//==============================================================================
563FlacAudioFormat::FlacAudioFormat() : AudioFormat (flacFormatName, ".flac") {}
564FlacAudioFormat::~FlacAudioFormat() {}
565
566Array<int> FlacAudioFormat::getPossibleSampleRates()
567{
568 return { 8000, 11025, 12000, 16000, 22050, 32000, 44100, 48000,
569 88200, 96000, 176400, 192000, 352800, 384000 };
570}
571
572Array<int> FlacAudioFormat::getPossibleBitDepths()
573{
574 return { 16, 24 };
575}
576
577bool FlacAudioFormat::canDoStereo() { return true; }
578bool FlacAudioFormat::canDoMono() { return true; }
579bool FlacAudioFormat::isCompressed() { return true; }
580
581AudioFormatReader* FlacAudioFormat::createReaderFor (InputStream* in, const bool deleteStreamIfOpeningFails)
582{
583 std::unique_ptr<FlacReader> r (new FlacReader (in));
584
585 if (r->sampleRate > 0)
586 return r.release();
587
588 if (! deleteStreamIfOpeningFails)
589 r->input = nullptr;
590
591 return nullptr;
592}
593
594AudioFormatWriter* FlacAudioFormat::createWriterFor (OutputStream* out,
595 double sampleRate,
596 unsigned int numberOfChannels,
597 int bitsPerSample,
598 const StringPairArray& /*metadataValues*/,
599 int qualityOptionIndex)
600{
601 if (out != nullptr && getPossibleBitDepths().contains (bitsPerSample))
602 {
603 std::unique_ptr<FlacWriter> w (new FlacWriter (out, sampleRate, numberOfChannels,
604 (uint32) bitsPerSample, qualityOptionIndex));
605 if (w->ok)
606 return w.release();
607 }
608
609 return nullptr;
610}
611
612StringArray FlacAudioFormat::getQualityOptions()
613{
614 return { "0 (Fastest)", "1", "2", "3", "4", "5 (Default)","6", "7", "8 (Highest quality)" };
615}
616
617#endif
618
619} // namespace juce
juce::Range::emptyRange
static constexpr Range emptyRange(const ValueType start) noexcept
Definition juce_Range.h:73