Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
path: root/source/core/StarAudio.cpp
blob: b7da7736f11be7a4894d8357e479b31f1efeb279 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
// Fixes unused variable warning
#define OV_EXCLUDE_STATIC_CALLBACKS

#include "vorbis/codec.h"
#include "vorbis/vorbisfile.h"

#include "StarAudio.hpp"
#include "StarBuffer.hpp"
#include "StarIODeviceCallbacks.hpp"
#include "StarFile.hpp"
#include "StarFormat.hpp"
#include "StarLogging.hpp"
#include "StarDataStreamDevices.hpp"
#include "StarSha256.hpp"
#include "StarEncode.hpp"

namespace Star {

float const DefaultPerceptualRangeDb = 40.f;
float const DefaultPerceptualBoostRangeDb = 6.f;
// https://github.com/discord/perceptual
float perceptualToAmplitude(float perceptual, float normalizedMax, float range, float boostRange) {
  if (perceptual == 0.f) return 0.f;
  float dB = perceptual > normalizedMax
    ? ((perceptual - normalizedMax) / normalizedMax) * boostRange
    : (perceptual / normalizedMax) * range - range;
  return normalizedMax * pow(10.f, dB / 20.f);
}

float amplitudeToPerceptual(float amp, float normalizedMax, float range, float boostRange) {
  if (amp == 0.f) return 0.f;
  float const dB = 20.f * log10(amp / normalizedMax);
  float perceptual = dB > 0.f
    ? dB / boostRange + 1
    : (range + dB) / range;
  return normalizedMax * perceptual;
}

namespace {
  struct WaveData {
#ifdef STAR_STREAM_AUDIO
    IODevicePtr device;
    unsigned channels;
    unsigned sampleRate;
    size_t dataSize; // get the data size from the header to avoid id3 tag
#else
    ByteArrayPtr byteArray;
    unsigned channels;
    unsigned sampleRate;
#endif
  };

  template <typename T>
  T readLEType(IODevicePtr const& device) {
    T t;
    device->readFull((char*)&t, sizeof(t));
    fromByteOrder(ByteOrder::LittleEndian, (char*)&t, sizeof(t));
    return t;
  }

  bool isUncompressed(IODevicePtr device) {
    const size_t sigLength = 4;
    unique_ptr<char[]> riffSig(new char[sigLength + 1]()); // RIFF\0
    unique_ptr<char[]> waveSig(new char[sigLength + 1]()); // WAVE\0

    StreamOffset previousOffset = device->pos();
    device->seek(0);
    device->readFull(riffSig.get(), sigLength);
    device->seek(4, IOSeek::Relative);
    device->readFull(waveSig.get(), sigLength);
    device->seek(previousOffset);
    if (strcmp(riffSig.get(), "RIFF") == 0 && strcmp(waveSig.get(), "WAVE") == 0) { // bytes are magic
      return true;
    }
    return false;
  }

  WaveData parseWav(IODevicePtr device) {
    const size_t sigLength = 4;
    unique_ptr<char[]> riffSig(new char[sigLength + 1]()); // RIFF\0
    unique_ptr<char[]> waveSig(new char[sigLength + 1]()); // WAVE\0
    unique_ptr<char[]> fmtSig(new char[sigLength + 1]()); // fmt \0
    unique_ptr<char[]> dataSig(new char[sigLength + 1]()); // data\0

    // RIFF Chunk Descriptor
    device->seek(0);
    device->readFull(riffSig.get(), sigLength);

    uint32_t fileSize = readLEType<uint32_t>(device);
    fileSize += sigLength + sizeof(fileSize);
    if (fileSize != device->size())
      throw AudioException(strf("Wav file is wrong size, reports {} is actually {}", fileSize, device->size()));

    device->readFull(waveSig.get(), sigLength);

    if ((strcmp(riffSig.get(), "RIFF") != 0) || (strcmp(waveSig.get(), "WAVE") != 0)) { // bytes are not magic
      auto p = [](char a) { return isprint(a) ? a : '?'; };
      throw AudioException(strf("Wav file has wrong magic bytes, got `{:c}{:c}{:c}{:c}' and `{:c}{:c}{:c}{:c}' but expected `RIFF' and `WAVE'",
              p(riffSig[0]), p(riffSig[1]), p(riffSig[2]), p(riffSig[3]), p(waveSig[0]), p(waveSig[1]), p(waveSig[2]), p(waveSig[3])));
    }

    // fmt subchunk

    device->readFull(fmtSig.get(), sigLength);
    if (strcmp(fmtSig.get(), "fmt ") != 0) { // friendship is magic
      auto p = [](char a) { return isprint(a) ? a : '?'; };
      throw AudioException(strf("Wav file fmt subchunk has wrong magic bytes, got `{:c}{:c}{:c}{:c}' but expected `fmt '",
          p(fmtSig[0]),
          p(fmtSig[1]),
          p(fmtSig[2]),
          p(fmtSig[3])));
    }

    uint32_t fmtSubchunkSize = readLEType<uint32_t>(device);
    fmtSubchunkSize += sigLength;
    if (fmtSubchunkSize < 20)
      throw AudioException(strf("fmt subchunk is sized wrong, expected 20 got {}.  Is this wav file not PCM?", fmtSubchunkSize));

    uint16_t audioFormat = readLEType<uint16_t>(device);
    if (audioFormat != 1)
      throw AudioException("audioFormat data indicates that wav file is something other than PCM format.  Unsupported.");

    uint16_t wavChannels = readLEType<uint16_t>(device);
    uint32_t wavSampleRate = readLEType<uint32_t>(device);
    uint32_t wavByteRate = readLEType<uint32_t>(device);
    uint16_t wavBlockAlign = readLEType<uint16_t>(device);
    uint16_t wavBitsPerSample = readLEType<uint16_t>(device);

    if (wavBitsPerSample != 16)
      throw AudioException("Only 16-bit PCM wavs are supported.");
    if (wavByteRate * 8 != wavSampleRate * wavChannels * wavBitsPerSample)
      throw AudioException("Sanity check failed, ByteRate is wrong");
    if (wavBlockAlign * 8 != wavChannels * wavBitsPerSample)
      throw AudioException("Sanity check failed, BlockAlign is wrong");

    device->seek(fmtSubchunkSize - 20, IOSeek::Relative);

    // data subchunk

    device->readFull(dataSig.get(), sigLength);
    if (strcmp(dataSig.get(), "data") != 0) { // magic or more magic?
      auto p = [](char a) { return isprint(a) ? a : '?'; };
      throw AudioException(strf("Wav file data subchunk has wrong magic bytes, got `{:c}{:c}{:c}{:c}' but expected `data'",
          p(dataSig[0]), p(dataSig[1]), p(dataSig[2]), p(dataSig[3])));
    }

    uint32_t wavDataSize = readLEType<uint32_t>(device);
    size_t wavDataOffset = (size_t)device->pos();
    if (wavDataSize + wavDataOffset > (size_t)device->size()) {
      throw AudioException(strf("Wav file data size reported is inconsistent with file size, got {} but expected {}",
          device->size(), wavDataSize + wavDataOffset));
    }

    #ifdef STAR_STREAM_AUDIO
    // Return the original device positioned at the PCM data
    // Note: This means the caller owns handling endianness conversion
    device->seek(wavDataOffset);
    
    return WaveData{device, wavChannels, wavSampleRate, wavDataSize};
    #else
    ByteArrayPtr pcmData = make_shared<ByteArray>();
    pcmData->resize(wavDataSize);

    // Copy across data and perform and endianess conversion if needed

    device->readFull(pcmData->ptr(), pcmData->size());
    for (size_t i = 0; i < pcmData->size() / 2; ++i)
      fromByteOrder(ByteOrder::LittleEndian, pcmData->ptr() + i * 2, 2);

    return WaveData{std::move(pcmData), wavChannels, wavSampleRate};
    #endif
  }
}

class CompressedAudioImpl {
public:
  #ifdef STAR_STREAM_AUDIO
  CompressedAudioImpl(CompressedAudioImpl const& impl)
      : m_audioData(impl.m_audioData->clone())// Clone instead of sharing
        ,
        m_deviceCallbacks(m_audioData)// Pass reference to cloned data
        ,
        m_vorbisInfo(nullptr) {
    setupCallbacks();

    // Make sure data stream is ready to be read
    m_audioData->open(IOMode::Read);
    m_audioData->seek(0);

    // Add error checking to see what's happening with the clone
    if (!m_audioData->isOpen())
      throw AudioException("Failed to open cloned audio device");

    auto size = m_audioData->size();
    if (size <= 0)
      throw AudioException("Cloned audio device has no data");
  }

  CompressedAudioImpl(IODevicePtr audioData)
      : m_audioData(audioData->clone())// Clone instead of taking ownership
        ,
        m_deviceCallbacks(m_audioData)// Pass reference
        ,
        m_vorbisInfo(nullptr) {
    setupCallbacks();
    m_audioData->open(IOMode::Read);
    m_audioData->seek(0);
  }
  #else
  static size_t readFunc(void* ptr, size_t size, size_t nmemb, void* datasource) {
    return static_cast<ExternalBuffer*>(datasource)->read((char*)ptr, size * nmemb) / size;
  }

  static int seekFunc(void* datasource, ogg_int64_t offset, int whence) {
    static_cast<ExternalBuffer*>(datasource)->seek(offset, (IOSeek)whence);
    return 0;
  };

  static long int tellFunc(void* datasource) {
    return (long int)static_cast<ExternalBuffer*>(datasource)->pos();
  };

    CompressedAudioImpl(CompressedAudioImpl const& impl) {
    m_audioData = impl.m_audioData;
    m_memoryFile.reset(m_audioData->ptr(), m_audioData->size());
    m_vorbisInfo = nullptr;
  }

  CompressedAudioImpl(IODevicePtr audioData) {
    audioData->open(IOMode::Read);
    audioData->seek(0);
    m_audioData = make_shared<ByteArray>(audioData->readBytes((size_t)audioData->size()));
    m_memoryFile.reset(m_audioData->ptr(), m_audioData->size());
    m_vorbisInfo = nullptr;
  }
  #endif

  ~CompressedAudioImpl() {
    ov_clear(&m_vorbisFile);
  }

  #ifdef STAR_STREAM_AUDIO
  void setupCallbacks() {
    m_deviceCallbacks.setupOggCallbacks(m_callbacks);
  }
  #endif

  bool open() {
    #ifdef STAR_STREAM_AUDIO
    int result = ov_open_callbacks(&m_deviceCallbacks, &m_vorbisFile, NULL, 0, m_callbacks);
    if (result < 0) {
      Logger::error("Failed to open ogg stream: error code {}", result);
      return false;
    }
    #else
    m_callbacks.read_func = readFunc;
    m_callbacks.seek_func = seekFunc;
    m_callbacks.tell_func = tellFunc;
    m_callbacks.close_func = NULL;

    if (ov_open_callbacks(&m_memoryFile, &m_vorbisFile, NULL, 0, m_callbacks) < 0)
      return false;
    #endif

    m_vorbisInfo = ov_info(&m_vorbisFile, -1);
    return true;
  }

  unsigned channels() {
    return m_vorbisInfo->channels;
  }

  unsigned sampleRate() {
    return m_vorbisInfo->rate;
  }

  double totalTime() {
    return ov_time_total(&m_vorbisFile, -1);
  }

  uint64_t totalSamples() {
    return ov_pcm_total(&m_vorbisFile, -1);
  }

  void seekTime(double time) {
    int ret = ov_time_seek(&m_vorbisFile, time);

    if (ret != 0)
      throw StarException("Cannot seek ogg stream Audio::seekTime");
  }

  void seekSample(uint64_t pos) {
    int ret = ov_pcm_seek(&m_vorbisFile, pos);

    if (ret != 0)
      throw StarException("Cannot seek ogg stream in Audio::seekSample");
  }

  double currentTime() {
    return ov_time_tell(&m_vorbisFile);
  }

  uint64_t currentSample() {
    return ov_pcm_tell(&m_vorbisFile);
  }

  size_t readPartial(int16_t* buffer, size_t bufferSize) {
    int bitstream;
    int read = OV_HOLE;
    // ov_read takes int parameter, so do some magic here to make sure we don't
    // overflow
    bufferSize *= 2;
    do {
#if STAR_LITTLE_ENDIAN
      read = ov_read(&m_vorbisFile, (char*)buffer, bufferSize, 0, 2, 1, &bitstream);
#else
      read = ov_read(&m_vorbisFile, (char*)buffer, bufferSize, 1, 2, 1, &bitstream);
#endif
    } while (read == OV_HOLE);
    if (read < 0)
      throw AudioException::format("Error in Audio::read ({})", read);
    
    // read in bytes, returning number of int16_t samples.
    return read / 2;
  }
  
private:
  #ifdef STAR_STREAM_AUDIO
  IODevicePtr m_audioData;  
  IODeviceCallbacks m_deviceCallbacks;
  #else
  ByteArrayConstPtr m_audioData;
  ExternalBuffer m_memoryFile;
  #endif
  ov_callbacks m_callbacks;
  OggVorbis_File m_vorbisFile;
  vorbis_info* m_vorbisInfo;
};

class UncompressedAudioImpl {
public:
  #ifdef STAR_STREAM_AUDIO
  UncompressedAudioImpl(UncompressedAudioImpl const& impl)
    : m_device(impl.m_device->clone())
    , m_channels(impl.m_channels)
    , m_sampleRate(impl.m_sampleRate)
    , m_dataSize(impl.m_dataSize)
    , m_dataStart(impl.m_dataStart)

  {
    StreamOffset initialPos = m_device->pos(); // Store initial position
    if (!m_device->isOpen())
      m_device->open(IOMode::Read);
    m_device->seek(initialPos); // Restore position after open
  }
  
  UncompressedAudioImpl(CompressedAudioImpl& impl) {
    m_channels = impl.channels();
    m_sampleRate = impl.sampleRate();

    // Create a memory buffer to store decompressed data
    auto memDevice = make_shared<Buffer>();

    int16_t buffer[1024];
    while (true) {
      size_t ramt = impl.readPartial(buffer, 1024);
      if (ramt == 0)
        break;
      memDevice->writeFull((char*)buffer, ramt * 2);
    }

    m_device = memDevice;
  }

  UncompressedAudioImpl(IODevicePtr device, unsigned channels, unsigned sampleRate, size_t dataSize)
    : m_device(std::move(device))
    , m_channels(channels)
    , m_sampleRate(sampleRate)
    , m_dataSize(dataSize)
    , m_dataStart((size_t)m_device->pos())  // Store current position as data start
  {
    if (!m_device->isOpen())
      m_device->open(IOMode::Read);
  }
  #else
  UncompressedAudioImpl(UncompressedAudioImpl const& impl) {
    m_channels = impl.m_channels;
    m_sampleRate = impl.m_sampleRate;
    m_audioData = impl.m_audioData;
    m_memoryFile.reset(m_audioData->ptr(), m_audioData->size());
  }

  UncompressedAudioImpl(CompressedAudioImpl& impl) {
    m_channels = impl.channels();
    m_sampleRate = impl.sampleRate();

    int16_t buffer[1024];
    Buffer uncompressBuffer;
    while (true) {
      size_t ramt = impl.readPartial(buffer, 1024);

      if (ramt == 0) {
        // End of stream reached
        break;
      } else {
        uncompressBuffer.writeFull((char*)buffer, ramt * 2);
      }
    }

    m_audioData = make_shared<ByteArray>(uncompressBuffer.takeData());
    m_memoryFile.reset(m_audioData->ptr(), m_audioData->size());
  }

  UncompressedAudioImpl(ByteArrayConstPtr data, unsigned channels, unsigned sampleRate) {
    m_channels = channels;
    m_sampleRate = sampleRate;
    m_audioData = std::move(data);
    m_memoryFile.reset(m_audioData->ptr(), m_audioData->size());
  }
  #endif

  bool open() {
    return true;
  }

  unsigned channels() {
    return m_channels;
  }

  unsigned sampleRate() {
    return m_sampleRate;
  }

  double totalTime() {
    return (double)totalSamples() / m_sampleRate;
  }

  uint64_t totalSamples() {
    #ifdef STAR_STREAM_AUDIO
    return m_device->size() / 2 / m_channels;
    #else
    return m_memoryFile.dataSize() / 2 / m_channels;
    #endif
  }

  void seekTime(double time) {
    seekSample((uint64_t)(time * m_sampleRate));
  }

  void seekSample(uint64_t pos) {
    #ifdef STAR_STREAM_AUDIO
    m_device->seek(pos * 2 * m_channels);
    #else
    m_memoryFile.seek(pos * 2 * m_channels);
    #endif
  }

  double currentTime() {
    return (double)currentSample() / m_sampleRate;
  }

  uint64_t currentSample() {
    #ifdef STAR_STREAM_AUDIO
    return m_device->pos() / 2 / m_channels;
    #else
    return m_memoryFile.pos() / 2 / m_channels;
    #endif
  }


  size_t readPartial(int16_t* buffer, size_t bufferSize) {
    if (bufferSize != NPos)
      bufferSize = bufferSize * 2;
    #ifndef STAR_STREAM_AUDIO
    return m_memoryFile.read((char*)buffer, bufferSize) / 2;
    #else
    // Calculate remaining valid data
    size_t currentPos = m_device->pos() - m_dataStart;
    size_t remainingBytes = m_dataSize - currentPos;
    
    // Limit read to remaining valid data
    if (bufferSize > remainingBytes)
      bufferSize = remainingBytes;
      
    if (bufferSize == 0)
      return 0;
    
    size_t bytesRead = m_device->read((char*)buffer, bufferSize);
    
    // Handle endianness conversion
    for (size_t i = 0; i < bytesRead / 2; ++i)
      fromByteOrder(ByteOrder::LittleEndian, ((char*)buffer) + i * 2, 2);
      
    return bytesRead / 2;
    #endif
  }

private:
  #ifdef STAR_STREAM_AUDIO
  IODevicePtr m_device;
  #endif
  unsigned m_channels;
  unsigned m_sampleRate;
  #ifdef STAR_STREAM_AUDIO
  size_t m_dataSize;
  size_t m_dataStart;
  #else
  ByteArrayConstPtr m_audioData;
  ExternalBuffer m_memoryFile;
  #endif
};

Audio::Audio(IODevicePtr device, String name) {
  m_name = name;
  if (!device->isOpen())
    device->open(IOMode::Read);

  if (isUncompressed(device)) {
    WaveData data = parseWav(device);
    #ifdef STAR_STREAM_AUDIO
    m_uncompressed = make_shared<UncompressedAudioImpl>(std::move(data.device), data.channels, data.sampleRate, data.dataSize);
    #else
    m_uncompressed = make_shared<UncompressedAudioImpl>(std::move(data.byteArray), data.channels, data.sampleRate);
    #endif
  } else {
    m_compressed = make_shared<CompressedAudioImpl>(device);
    if (!m_compressed->open())
      throw AudioException("File does not appear to be a valid ogg bitstream");
  }
}

Audio::Audio(Audio const& audio) {
  *this = audio;
}

Audio::Audio(Audio&& audio) {
  operator=(std::move(audio));
}

Audio& Audio::operator=(Audio const& audio) {
    if (audio.m_uncompressed) {
        m_uncompressed = make_shared<UncompressedAudioImpl>(*audio.m_uncompressed);
        if (!m_uncompressed->open())
          throw AudioException("Failed to open uncompressed audio stream during copy");
    } else {
        m_compressed = make_shared<CompressedAudioImpl>(*audio.m_compressed);
        if (!m_compressed->open()) 
            throw AudioException("Failed to open compressed audio stream during copy");
    }

    seekSample(audio.currentSample());
    return *this;
}

Audio& Audio::operator=(Audio&& audio) {
  m_compressed = std::move(audio.m_compressed);
  m_uncompressed = std::move(audio.m_uncompressed);
  return *this;
}

unsigned Audio::channels() const {
  if (m_uncompressed)
    return m_uncompressed->channels();
  else
    return m_compressed->channels();
}

unsigned Audio::sampleRate() const {
  if (m_uncompressed)
    return m_uncompressed->sampleRate();
  else
    return m_compressed->sampleRate();
}

double Audio::totalTime() const {
  if (m_uncompressed)
    return m_uncompressed->totalTime();
  else
    return m_compressed->totalTime();
}

uint64_t Audio::totalSamples() const {
  if (m_uncompressed)
    return m_uncompressed->totalSamples();
  else
    return m_compressed->totalSamples();
}

bool Audio::compressed() const {
  return (bool)m_compressed;
}

void Audio::uncompress() {
  if (m_compressed) {
    m_uncompressed = make_shared<UncompressedAudioImpl>(*m_compressed);
    m_compressed.reset();
  }
}

void Audio::seekTime(double time) {
  if (m_uncompressed)
    m_uncompressed->seekTime(time);
  else
    m_compressed->seekTime(time);
}

void Audio::seekSample(uint64_t pos) {
  if (m_uncompressed)
    m_uncompressed->seekSample(pos);
  else
    m_compressed->seekSample(pos);
}

double Audio::currentTime() const {
  if (m_uncompressed)
    return m_uncompressed->currentTime();
  else
    return m_compressed->currentTime();
}

uint64_t Audio::currentSample() const {
  if (m_uncompressed)
    return m_uncompressed->currentSample();
  else
    return m_compressed->currentSample();
}

size_t Audio::readPartial(int16_t* buffer, size_t bufferSize) {
  if (bufferSize == 0)
    return 0;

  if (m_uncompressed)
    return m_uncompressed->readPartial(buffer, bufferSize);
  else
    return m_compressed->readPartial(buffer, bufferSize);
}

size_t Audio::read(int16_t* buffer, size_t bufferSize) {
  if (bufferSize == 0)
    return 0;

  size_t readTotal = 0;
  while (readTotal < bufferSize) {
    size_t toGo = bufferSize - readTotal;
    size_t ramt = readPartial(buffer + readTotal, toGo);
    readTotal += ramt;
    // End of stream reached
    if (ramt == 0)
      break;
  }
  return readTotal;
}

size_t Audio::resample(unsigned destinationChannels, unsigned destinationSampleRate, int16_t* destinationBuffer, size_t destinationBufferSize, double velocity) {
  unsigned destinationSamples = destinationBufferSize / destinationChannels;
  if (destinationSamples == 0)
    return 0;

  unsigned sourceChannels = channels();
  unsigned sourceSampleRate = sampleRate();

  if (velocity != 1.0)
    sourceSampleRate = (unsigned)(sourceSampleRate * velocity);

  if (destinationChannels == sourceChannels && destinationSampleRate == sourceSampleRate) {
    // If the destination and source channel count and sample rate are the
    // same, this is the same as a read.

    return read(destinationBuffer, destinationBufferSize);

  } else if (destinationSampleRate == sourceSampleRate) {
    // If the destination and source sample rate are the same, then we can skip
    // the super-sampling math.

    unsigned sourceBufferSize = destinationSamples * sourceChannels;

    m_workingBuffer.resize(sourceBufferSize * sizeof(int16_t));
    int16_t* sourceBuffer = (int16_t*)m_workingBuffer.ptr();

    unsigned readSamples = read(sourceBuffer, sourceBufferSize) / sourceChannels;

    for (unsigned sample = 0; sample < readSamples; ++sample) {
      unsigned sourceBufferIndex = sample * sourceChannels;
      unsigned destinationBufferIndex = sample * destinationChannels;

      for (unsigned destinationChannel = 0; destinationChannel < destinationChannels; ++destinationChannel) {
        // If the destination channel count is greater than the source
        // channels, simply copy the last channel
        unsigned sourceChannel = min(destinationChannel, sourceChannels - 1);
        destinationBuffer[destinationBufferIndex + destinationChannel] =
            sourceBuffer[sourceBufferIndex + sourceChannel];
      }
    }

    return readSamples * destinationChannels;

  } else {
    // Otherwise, we have to do a full resample.

    unsigned sourceSamples = ((uint64_t)sourceSampleRate * destinationSamples + destinationSampleRate - 1) / destinationSampleRate;
    unsigned sourceBufferSize = sourceSamples * sourceChannels;

    m_workingBuffer.resize(sourceBufferSize * sizeof(int16_t));
    int16_t* sourceBuffer = (int16_t*)m_workingBuffer.ptr();

    unsigned readSamples = read(sourceBuffer, sourceBufferSize) / sourceChannels;

    if (readSamples == 0)
      return 0;

    unsigned writtenSamples = 0;

    for (unsigned destinationSample = 0; destinationSample < destinationSamples; ++destinationSample) {
      unsigned destinationBufferIndex = destinationSample * destinationChannels;

      for (unsigned destinationChannel = 0; destinationChannel < destinationChannels; ++destinationChannel) {
        static int const SuperSampleFactor = 8;

        // If the destination channel count is greater than the source
        // channels, simply copy the last channel
        unsigned sourceChannel = min(destinationChannel, sourceChannels - 1);

        int sample = 0;
        int sampleCount = 0;
        for (int superSample = 0; superSample < SuperSampleFactor; ++superSample) {
          unsigned sourceSample = (unsigned)((destinationSample * SuperSampleFactor + superSample) * sourceSamples / destinationSamples) / SuperSampleFactor;
          if (sourceSample < readSamples) {
            unsigned sourceBufferIndex = sourceSample * sourceChannels;
            starAssert(sourceBufferIndex + sourceChannel < sourceBufferSize);
            sample += sourceBuffer[sourceBufferIndex + sourceChannel];
            ++sampleCount;
          }
        }

        // If sampleCount is zero, then we are past the end of our read data
        // completely, and can stop
        if (sampleCount == 0)
          return writtenSamples * destinationChannels;

        sample /= sampleCount;
        destinationBuffer[destinationBufferIndex + destinationChannel] = (int16_t)sample;
        writtenSamples = destinationSample + 1;
      }
    }

    return writtenSamples * destinationChannels;
  }
}

String const& Audio::name() const {
  return m_name;
}

void Audio::setName(String name) {
  m_name = std::move(name);
}

}