OpenShot Audio Library | OpenShotAudio  0.6.0
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 
26 namespace 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 
88 namespace juce
89 {
90 
91 namespace 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 //==============================================================================
190 static const char* const flacFormatName = "FLAC file";
191 
192 template <typename Item>
193 auto emptyRange (Item item) { return Range<Item>::emptyRange (item); }
194 
195 //==============================================================================
196 class FlacReader final : public AudioFormatReader
197 {
198 public:
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 
388 private:
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 //==============================================================================
399 class FlacWriter final : public AudioFormatWriter
400 {
401 public:
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 
554 private:
555  FlacNamespace::FLAC__StreamEncoder* encoder;
556  int64 streamStartPos;
557 
558  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlacWriter)
559 };
560 
561 
562 //==============================================================================
563 FlacAudioFormat::FlacAudioFormat() : AudioFormat (flacFormatName, ".flac") {}
564 FlacAudioFormat::~FlacAudioFormat() {}
565 
566 Array<int> FlacAudioFormat::getPossibleSampleRates()
567 {
568  return { 8000, 11025, 12000, 16000, 22050, 32000, 44100, 48000,
569  88200, 96000, 176400, 192000, 352800, 384000 };
570 }
571 
572 Array<int> FlacAudioFormat::getPossibleBitDepths()
573 {
574  return { 16, 24 };
575 }
576 
577 bool FlacAudioFormat::canDoStereo() { return true; }
578 bool FlacAudioFormat::canDoMono() { return true; }
579 bool FlacAudioFormat::isCompressed() { return true; }
580 
581 AudioFormatReader* 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 
594 AudioFormatWriter* 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 
612 StringArray 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
constexpr static Range emptyRange(const ValueType start) noexcept
Definition: juce_Range.h:73
static Range< Index > doBufferedRead(Range< Index > rangeToRead, GetBufferedRange &&getBufferedRange, ReadFromReservoir &&readFromReservoir, FillReservoir &&fillReservoir)