| @@ -6927,6 +6927,12 @@ void OutputStream::writeByte (char byte) | |||
| write (&byte, 1); | |||
| } | |||
| void OutputStream::writeRepeatedByte (uint8 byte, int numTimesToRepeat) | |||
| { | |||
| while (--numTimesToRepeat >= 0) | |||
| writeByte (byte); | |||
| } | |||
| void OutputStream::writeShort (short value) | |||
| { | |||
| const unsigned short v = ByteOrder::swapIfBigEndian ((unsigned short) value); | |||
| @@ -20729,10 +20735,6 @@ public: | |||
| } | |||
| } | |||
| ~AiffAudioFormatReader() | |||
| { | |||
| } | |||
| bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | |||
| int64 startSampleInFile, int numSamples) | |||
| { | |||
| @@ -23091,6 +23093,9 @@ const StringPairArray WavAudioFormat::createBWAVMetadata (const String& descript | |||
| return m; | |||
| } | |||
| namespace WavFileHelpers | |||
| { | |||
| #if JUCE_MSVC | |||
| #pragma pack (push, 1) | |||
| #define PACKED | |||
| @@ -23265,12 +23270,26 @@ struct ExtensibleWavSubFormat | |||
| uint8 data4[8]; | |||
| } PACKED; | |||
| struct DataSize64Chunk // chunk ID = 'ds64' if data size > 0xffffffff, 'JUNK' otherwise | |||
| { | |||
| uint32 riffSizeLow; // low 4 byte size of RF64 block | |||
| uint32 riffSizeHigh; // high 4 byte size of RF64 block | |||
| uint32 dataSizeLow; // low 4 byte size of data chunk | |||
| uint32 dataSizeHigh; // high 4 byte size of data chunk | |||
| uint32 sampleCountLow; // low 4 byte sample count of fact chunk | |||
| uint32 sampleCountHigh; // high 4 byte sample count of fact chunk | |||
| uint32 tableLength; // number of valid entries in array 'table' | |||
| } PACKED; | |||
| #if JUCE_MSVC | |||
| #pragma pack (pop) | |||
| #endif | |||
| #undef PACKED | |||
| inline int chunkName (const char* const name) { return (int) ByteOrder::littleEndianInt (name); } | |||
| } | |||
| class WavAudioFormatReader : public AudioFormatReader | |||
| { | |||
| public: | |||
| @@ -23279,109 +23298,146 @@ public: | |||
| : AudioFormatReader (in, TRANS (wavFormatName)), | |||
| bwavChunkStart (0), | |||
| bwavSize (0), | |||
| dataLength (0) | |||
| dataLength (0), | |||
| isRF64 (false) | |||
| { | |||
| if (input->readInt() == chunkName ("RIFF")) | |||
| using namespace WavFileHelpers; | |||
| uint64 len = 0; | |||
| int64 end = 0; | |||
| bool hasGotType = false; | |||
| bool hasGotData = false; | |||
| const int firstChunkType = input->readInt(); | |||
| if (firstChunkType == chunkName ("RF64")) | |||
| { | |||
| const uint32 len = (uint32) input->readInt(); | |||
| const int64 end = input->getPosition() + len; | |||
| bool hasGotType = false; | |||
| bool hasGotData = false; | |||
| input->skipNextBytes (4); // size is -1 for RF64 | |||
| isRF64 = true; | |||
| } | |||
| else if (firstChunkType == chunkName ("RIFF")) | |||
| { | |||
| len = (uint64) input->readInt(); | |||
| end = input->getPosition() + len; | |||
| } | |||
| else | |||
| { | |||
| return; | |||
| } | |||
| if (input->readInt() == chunkName ("WAVE")) | |||
| const int64 startOfRIFFChunk = input->getPosition(); | |||
| if (input->readInt() == chunkName ("WAVE")) | |||
| { | |||
| if (isRF64 && input->readInt() == chunkName ("ds64")) | |||
| { | |||
| while (input->getPosition() < end | |||
| && ! input->isExhausted()) | |||
| uint32 length = (uint32) input->readInt(); | |||
| if (length < 28) | |||
| { | |||
| return; | |||
| } | |||
| else | |||
| { | |||
| const int chunkType = input->readInt(); | |||
| uint32 length = (uint32) input->readInt(); | |||
| const int64 chunkEnd = input->getPosition() + length + (length & 1); | |||
| len = input->readInt64(); | |||
| end = startOfRIFFChunk + len; | |||
| dataLength = input->readInt64(); | |||
| input->setPosition (chunkEnd); | |||
| } | |||
| } | |||
| if (chunkType == chunkName ("fmt ")) | |||
| { | |||
| // read the format chunk | |||
| const unsigned short format = input->readShort(); | |||
| const short numChans = input->readShort(); | |||
| sampleRate = input->readInt(); | |||
| const int bytesPerSec = input->readInt(); | |||
| while (input->getPosition() < end && ! input->isExhausted()) | |||
| { | |||
| const int chunkType = input->readInt(); | |||
| uint32 length = (uint32) input->readInt(); | |||
| const int64 chunkEnd = input->getPosition() + length + (length & 1); | |||
| numChannels = numChans; | |||
| bytesPerFrame = bytesPerSec / (int)sampleRate; | |||
| bitsPerSample = 8 * bytesPerFrame / numChans; | |||
| if (chunkType == chunkName ("fmt ")) | |||
| { | |||
| // read the format chunk | |||
| const unsigned short format = input->readShort(); | |||
| const short numChans = input->readShort(); | |||
| sampleRate = input->readInt(); | |||
| const int bytesPerSec = input->readInt(); | |||
| numChannels = numChans; | |||
| bytesPerFrame = bytesPerSec / (int)sampleRate; | |||
| bitsPerSample = 8 * bytesPerFrame / numChans; | |||
| if (format == 3) | |||
| if (format == 3) | |||
| { | |||
| usesFloatingPointData = true; | |||
| } | |||
| else if (format == 0xfffe /*WAVE_FORMAT_EXTENSIBLE*/) | |||
| { | |||
| if (length < 40) // too short | |||
| { | |||
| usesFloatingPointData = true; | |||
| bytesPerFrame = 0; | |||
| } | |||
| else if (format == 0xfffe /*WAVE_FORMAT_EXTENSIBLE*/) | |||
| else | |||
| { | |||
| if (length < 40) // too short | |||
| { | |||
| bytesPerFrame = 0; | |||
| } | |||
| else | |||
| { | |||
| input->skipNextBytes (12); // skip over blockAlign, bitsPerSample and speakerPosition mask | |||
| ExtensibleWavSubFormat subFormat; | |||
| subFormat.data1 = input->readInt(); | |||
| subFormat.data2 = input->readShort(); | |||
| subFormat.data3 = input->readShort(); | |||
| input->read (subFormat.data4, sizeof (subFormat.data4)); | |||
| input->skipNextBytes (12); // skip over blockAlign, bitsPerSample and speakerPosition mask | |||
| ExtensibleWavSubFormat subFormat; | |||
| subFormat.data1 = input->readInt(); | |||
| subFormat.data2 = input->readShort(); | |||
| subFormat.data3 = input->readShort(); | |||
| input->read (subFormat.data4, sizeof (subFormat.data4)); | |||
| const ExtensibleWavSubFormat pcmFormat | |||
| = { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; | |||
| const ExtensibleWavSubFormat pcmFormat | |||
| = { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; | |||
| if (memcmp (&subFormat, &pcmFormat, sizeof (subFormat)) != 0) | |||
| { | |||
| const ExtensibleWavSubFormat ambisonicFormat | |||
| = { 0x00000001, 0x0721, 0x11d3, { 0x86, 0x44, 0xC8, 0xC1, 0xCA, 0x00, 0x00, 0x00 } }; | |||
| if (memcmp (&subFormat, &pcmFormat, sizeof (subFormat)) != 0) | |||
| { | |||
| const ExtensibleWavSubFormat ambisonicFormat | |||
| = { 0x00000001, 0x0721, 0x11d3, { 0x86, 0x44, 0xC8, 0xC1, 0xCA, 0x00, 0x00, 0x00 } }; | |||
| if (memcmp (&subFormat, &ambisonicFormat, sizeof (subFormat)) != 0) | |||
| bytesPerFrame = 0; | |||
| } | |||
| if (memcmp (&subFormat, &ambisonicFormat, sizeof (subFormat)) != 0) | |||
| bytesPerFrame = 0; | |||
| } | |||
| } | |||
| else if (format != 1) | |||
| { | |||
| bytesPerFrame = 0; | |||
| } | |||
| hasGotType = true; | |||
| } | |||
| else if (chunkType == chunkName ("data")) | |||
| else if (format != 1) | |||
| { | |||
| // get the data chunk's position | |||
| bytesPerFrame = 0; | |||
| } | |||
| hasGotType = true; | |||
| } | |||
| else if (chunkType == chunkName ("data")) | |||
| { | |||
| // get the data chunk's position | |||
| if (! isRF64) // data size is expected to be -1, actual data size is in ds64 chunk | |||
| dataLength = length; | |||
| dataChunkStart = input->getPosition(); | |||
| lengthInSamples = (bytesPerFrame > 0) ? (dataLength / bytesPerFrame) : 0; | |||
| hasGotData = true; | |||
| } | |||
| else if (chunkType == chunkName ("bext")) | |||
| { | |||
| bwavChunkStart = input->getPosition(); | |||
| bwavSize = length; | |||
| // Broadcast-wav extension chunk.. | |||
| HeapBlock <BWAVChunk> bwav; | |||
| bwav.calloc (jmax ((size_t) length + 1, sizeof (BWAVChunk)), 1); | |||
| input->read (bwav, length); | |||
| bwav->copyTo (metadataValues); | |||
| } | |||
| else if (chunkType == chunkName ("smpl")) | |||
| { | |||
| HeapBlock <SMPLChunk> smpl; | |||
| smpl.calloc (jmax ((size_t) length + 1, sizeof (SMPLChunk)), 1); | |||
| input->read (smpl, length); | |||
| smpl->copyTo (metadataValues, length); | |||
| } | |||
| else if (chunkEnd <= input->getPosition()) | |||
| { | |||
| break; | |||
| } | |||
| dataChunkStart = input->getPosition(); | |||
| lengthInSamples = (bytesPerFrame > 0) ? (dataLength / bytesPerFrame) : 0; | |||
| input->setPosition (chunkEnd); | |||
| hasGotData = true; | |||
| } | |||
| else if (chunkType == chunkName ("bext")) | |||
| { | |||
| bwavChunkStart = input->getPosition(); | |||
| bwavSize = length; | |||
| // Broadcast-wav extension chunk.. | |||
| HeapBlock <BWAVChunk> bwav; | |||
| bwav.calloc (jmax ((size_t) length + 1, sizeof (BWAVChunk)), 1); | |||
| input->read (bwav, length); | |||
| bwav->copyTo (metadataValues); | |||
| } | |||
| else if (chunkType == chunkName ("smpl")) | |||
| { | |||
| HeapBlock <SMPLChunk> smpl; | |||
| smpl.calloc (jmax ((size_t) length + 1, sizeof (SMPLChunk)), 1); | |||
| input->read (smpl, length); | |||
| smpl->copyTo (metadataValues, length); | |||
| } | |||
| else if (chunkEnd <= input->getPosition()) | |||
| { | |||
| break; | |||
| } | |||
| input->setPosition (chunkEnd); | |||
| } | |||
| } | |||
| } | |||
| @@ -23443,8 +23499,7 @@ private: | |||
| ScopedPointer<AudioData::Converter> converter; | |||
| int bytesPerFrame; | |||
| int64 dataChunkStart, dataLength; | |||
| static inline int chunkName (const char* const name) { return (int) ByteOrder::littleEndianInt (name); } | |||
| bool isRF64; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatReader); | |||
| }; | |||
| @@ -23453,20 +23508,17 @@ class WavAudioFormatWriter : public AudioFormatWriter | |||
| { | |||
| public: | |||
| WavAudioFormatWriter (OutputStream* const out, | |||
| const double sampleRate_, | |||
| const unsigned int numChannels_, | |||
| const int bits, | |||
| WavAudioFormatWriter (OutputStream* const out, const double sampleRate_, | |||
| const unsigned int numChannels_, const int bits, | |||
| const StringPairArray& metadataValues) | |||
| : AudioFormatWriter (out, | |||
| TRANS (wavFormatName), | |||
| sampleRate_, | |||
| numChannels_, | |||
| bits), | |||
| : AudioFormatWriter (out, TRANS (wavFormatName), sampleRate_, numChannels_, bits), | |||
| lengthInSamples (0), | |||
| bytesWritten (0), | |||
| writeFailed (false) | |||
| writeFailed (false), | |||
| isRF64 (false) | |||
| { | |||
| using namespace WavFileHelpers; | |||
| if (metadataValues.size() > 0) | |||
| { | |||
| bwavChunk = BWAVChunk::createFrom (metadataValues); | |||
| @@ -23479,6 +23531,12 @@ public: | |||
| ~WavAudioFormatWriter() | |||
| { | |||
| if ((bytesWritten & 1) != 0) // pad to an even length | |||
| { | |||
| ++bytesWritten; | |||
| output->writeByte (0); | |||
| } | |||
| writeHeader(); | |||
| } | |||
| @@ -23501,8 +23559,7 @@ public: | |||
| default: jassertfalse; break; | |||
| } | |||
| if (bytesWritten + bytes >= (uint32) 0xfff00000 | |||
| || ! output->write (tempBlock.getData(), bytes)) | |||
| if (! output->write (tempBlock.getData(), bytes)) | |||
| { | |||
| // failed to write to disk, so let's try writing the header. | |||
| // If it's just run out of disk space, then if it does manage | |||
| @@ -23523,14 +23580,27 @@ public: | |||
| private: | |||
| ScopedPointer<AudioData::Converter> converter; | |||
| MemoryBlock tempBlock, bwavChunk, smplChunk; | |||
| uint32 lengthInSamples, bytesWritten; | |||
| uint64 lengthInSamples, bytesWritten; | |||
| int64 headerPosition; | |||
| bool writeFailed; | |||
| bool writeFailed, isRF64; | |||
| static inline int chunkName (const char* const name) { return (int) ByteOrder::littleEndianInt (name); } | |||
| static int getChannelMask (const int numChannels) throw() | |||
| { | |||
| switch (numChannels) | |||
| { | |||
| case 1: return 0; | |||
| case 2: return 1 + 2; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | |||
| case 5: return 1 + 2 + 4 + 16 + 32; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | |||
| case 6: return 1 + 2 + 4 + 8 + 16 + 32; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | |||
| default: break; | |||
| } | |||
| return 0; | |||
| } | |||
| void writeHeader() | |||
| { | |||
| using namespace WavFileHelpers; | |||
| const bool seekedOk = output->setPosition (headerPosition); | |||
| (void) seekedOk; | |||
| @@ -23539,21 +23609,62 @@ private: | |||
| jassert (seekedOk); | |||
| const int bytesPerFrame = numChannels * bitsPerSample / 8; | |||
| output->writeInt (chunkName ("RIFF")); | |||
| output->writeInt ((int) (lengthInSamples * bytesPerFrame | |||
| + ((bwavChunk.getSize() > 0) ? (44 + bwavChunk.getSize()) : 36) | |||
| + (smplChunk.getSize() > 0 ? smplChunk.getSize() + 8 : 0))); | |||
| int64 audioDataSize = bytesPerFrame * lengthInSamples; | |||
| int64 riffChunkSize = 4 /* 'WAVE' */ + 8 + 40 /* WAVEFORMATEX */ | |||
| + 8 + audioDataSize + (audioDataSize & 1) | |||
| + (bwavChunk.getSize() > 0 ? (8 + bwavChunk.getSize()) : 0) | |||
| + (smplChunk.getSize() > 0 ? (8 + smplChunk.getSize()) : 0) | |||
| + (8 + 28); // (JUNK chunk) | |||
| riffChunkSize += (riffChunkSize & 0x1); | |||
| isRF64 = (riffChunkSize > 0xffffffff); | |||
| output->writeInt (chunkName (isRF64 ? "RF64" : "RIFF")); | |||
| output->writeInt (isRF64 ? -1 : (int) riffChunkSize); | |||
| output->writeInt (chunkName ("WAVE")); | |||
| if (! isRF64) | |||
| { | |||
| // write Junk chunk | |||
| output->writeInt (chunkName ("JUNK")); | |||
| output->writeInt (28); | |||
| output->writeRepeatedByte (0, 28); | |||
| } | |||
| else | |||
| { | |||
| // write ds64 chunk | |||
| output->writeInt (chunkName ("ds64")); | |||
| output->writeInt (28); // chunk size for uncompressed data (no table) | |||
| output->writeInt64 (riffChunkSize); | |||
| output->writeInt64 (audioDataSize); | |||
| output->writeRepeatedByte (0, 12); | |||
| } | |||
| output->writeInt (chunkName ("fmt ")); | |||
| output->writeInt (16); | |||
| output->writeShort ((bitsPerSample < 32) ? (short) 1 /*WAVE_FORMAT_PCM*/ | |||
| : (short) 3 /*WAVE_FORMAT_IEEE_FLOAT*/); | |||
| output->writeInt (40); // WAVEFORMATEX chunk size | |||
| output->writeShort ((short) (uint16) 0xfffe); // WAVE_FORMAT_EXTENSIBLE | |||
| output->writeShort ((short) numChannels); | |||
| output->writeInt ((int) sampleRate); | |||
| output->writeInt (bytesPerFrame * (int) sampleRate); | |||
| output->writeShort ((short) bytesPerFrame); | |||
| output->writeShort ((short) bitsPerSample); | |||
| output->writeInt ((int) (bytesPerFrame * sampleRate)); // nAvgBytesPerSec | |||
| output->writeShort ((short) bytesPerFrame); // nBlockAlign | |||
| output->writeShort ((short) bitsPerSample); // wBitsPerSample | |||
| output->writeShort (22); // cbSize (size of the extension) | |||
| output->writeShort ((short) bitsPerSample); // wValidBitsPerSample | |||
| output->writeInt (getChannelMask (numChannels)); | |||
| const ExtensibleWavSubFormat pcmFormat | |||
| = { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; | |||
| const ExtensibleWavSubFormat IEEEFloatFormat | |||
| = { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; | |||
| const ExtensibleWavSubFormat& subFormat = bitsPerSample < 32 ? pcmFormat : IEEEFloatFormat; | |||
| output->writeInt ((int) subFormat.data1); | |||
| output->writeShort ((short) subFormat.data2); | |||
| output->writeShort ((short) subFormat.data3); | |||
| output->write (subFormat.data4, sizeof (subFormat.data4)); | |||
| if (bwavChunk.getSize() > 0) | |||
| { | |||
| @@ -23570,7 +23681,7 @@ private: | |||
| } | |||
| output->writeInt (chunkName ("data")); | |||
| output->writeInt (lengthInSamples * bytesPerFrame); | |||
| output->writeInt (isRF64 ? -1 : (int) (lengthInSamples * bytesPerFrame)); | |||
| usesFloatingPointData = (bitsPerSample == 32); | |||
| } | |||
| @@ -23616,28 +23727,19 @@ AudioFormatReader* WavAudioFormat::createReaderFor (InputStream* sourceStream, | |||
| return 0; | |||
| } | |||
| AudioFormatWriter* WavAudioFormat::createWriterFor (OutputStream* out, | |||
| double sampleRate, | |||
| unsigned int numChannels, | |||
| int bitsPerSample, | |||
| const StringPairArray& metadataValues, | |||
| int /*qualityOptionIndex*/) | |||
| AudioFormatWriter* WavAudioFormat::createWriterFor (OutputStream* out, double sampleRate, | |||
| unsigned int numChannels, int bitsPerSample, | |||
| const StringPairArray& metadataValues, int /*qualityOptionIndex*/) | |||
| { | |||
| if (getPossibleBitDepths().contains (bitsPerSample)) | |||
| { | |||
| return new WavAudioFormatWriter (out, | |||
| sampleRate, | |||
| numChannels, | |||
| bitsPerSample, | |||
| metadataValues); | |||
| } | |||
| return new WavAudioFormatWriter (out, sampleRate, numChannels, bitsPerSample, metadataValues); | |||
| return 0; | |||
| } | |||
| namespace | |||
| namespace WavFileHelpers | |||
| { | |||
| bool juce_slowCopyOfWavFileWithNewMetadata (const File& file, const StringPairArray& metadata) | |||
| bool slowCopyWavFileWithNewMetadata (const File& file, const StringPairArray& metadata) | |||
| { | |||
| TemporaryFile tempFile (file); | |||
| @@ -23673,7 +23775,8 @@ namespace | |||
| bool WavAudioFormat::replaceMetadataInFile (const File& wavFile, const StringPairArray& newMetadata) | |||
| { | |||
| ScopedPointer <WavAudioFormatReader> reader ((WavAudioFormatReader*) createReaderFor (wavFile.createInputStream(), true)); | |||
| using namespace WavFileHelpers; | |||
| ScopedPointer <WavAudioFormatReader> reader (static_cast <WavAudioFormatReader*> (createReaderFor (wavFile.createInputStream(), true))); | |||
| if (reader != 0) | |||
| { | |||
| @@ -23704,7 +23807,7 @@ bool WavAudioFormat::replaceMetadataInFile (const File& wavFile, const StringPai | |||
| } | |||
| } | |||
| return juce_slowCopyOfWavFileWithNewMetadata (wavFile, newMetadata); | |||
| return slowCopyWavFileWithNewMetadata (wavFile, newMetadata); | |||
| } | |||
| END_JUCE_NAMESPACE | |||
| @@ -23773,7 +23876,7 @@ AudioFormatReaderSource::~AudioFormatReaderSource() | |||
| delete reader; | |||
| } | |||
| void AudioFormatReaderSource::setNextReadPosition (int newPosition) | |||
| void AudioFormatReaderSource::setNextReadPosition (int64 newPosition) | |||
| { | |||
| nextPlayPos = newPosition; | |||
| } | |||
| @@ -23783,15 +23886,15 @@ void AudioFormatReaderSource::setLooping (bool shouldLoop) | |||
| looping = shouldLoop; | |||
| } | |||
| int AudioFormatReaderSource::getNextReadPosition() const | |||
| int64 AudioFormatReaderSource::getNextReadPosition() const | |||
| { | |||
| return (looping) ? (nextPlayPos % (int) reader->lengthInSamples) | |||
| : nextPlayPos; | |||
| return looping ? nextPlayPos % reader->lengthInSamples | |||
| : nextPlayPos; | |||
| } | |||
| int AudioFormatReaderSource::getTotalLength() const | |||
| int64 AudioFormatReaderSource::getTotalLength() const | |||
| { | |||
| return (int) reader->lengthInSamples; | |||
| return reader->lengthInSamples; | |||
| } | |||
| void AudioFormatReaderSource::prepareToPlay (int /*samplesPerBlockExpected*/, | |||
| @@ -23807,7 +23910,7 @@ void AudioFormatReaderSource::getNextAudioBlock (const AudioSourceChannelInfo& i | |||
| { | |||
| if (info.numSamples > 0) | |||
| { | |||
| const int start = nextPlayPos; | |||
| const int64 start = nextPlayPos; | |||
| if (looping) | |||
| { | |||
| @@ -24152,7 +24255,7 @@ void AudioTransportSource::stop() | |||
| void AudioTransportSource::setPosition (double newPosition) | |||
| { | |||
| if (sampleRate > 0.0) | |||
| setNextReadPosition (roundToInt (newPosition * sampleRate)); | |||
| setNextReadPosition ((int64) (newPosition * sampleRate)); | |||
| } | |||
| double AudioTransportSource::getCurrentPosition() const | |||
| @@ -24168,30 +24271,30 @@ double AudioTransportSource::getLengthInSeconds() const | |||
| return getTotalLength() / sampleRate; | |||
| } | |||
| void AudioTransportSource::setNextReadPosition (int newPosition) | |||
| void AudioTransportSource::setNextReadPosition (int64 newPosition) | |||
| { | |||
| if (positionableSource != 0) | |||
| { | |||
| if (sampleRate > 0 && sourceSampleRate > 0) | |||
| newPosition = roundToInt (newPosition * sourceSampleRate / sampleRate); | |||
| newPosition = (int64) (newPosition * sourceSampleRate / sampleRate); | |||
| positionableSource->setNextReadPosition (newPosition); | |||
| } | |||
| } | |||
| int AudioTransportSource::getNextReadPosition() const | |||
| int64 AudioTransportSource::getNextReadPosition() const | |||
| { | |||
| if (positionableSource != 0) | |||
| { | |||
| const double ratio = (sampleRate > 0 && sourceSampleRate > 0) ? sampleRate / sourceSampleRate : 1.0; | |||
| return roundToInt (positionableSource->getNextReadPosition() * ratio); | |||
| return (int64) (positionableSource->getNextReadPosition() * ratio); | |||
| } | |||
| return 0; | |||
| } | |||
| int AudioTransportSource::getTotalLength() const | |||
| int64 AudioTransportSource::getTotalLength() const | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| @@ -24199,7 +24302,7 @@ int AudioTransportSource::getTotalLength() const | |||
| { | |||
| const double ratio = (sampleRate > 0 && sourceSampleRate > 0) ? sampleRate / sourceSampleRate : 1.0; | |||
| return roundToInt (positionableSource->getTotalLength() * ratio); | |||
| return (int64) (positionableSource->getTotalLength() * ratio); | |||
| } | |||
| return 0; | |||
| @@ -24446,8 +24549,8 @@ void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info | |||
| { | |||
| const ScopedLock sl (bufferStartPosLock); | |||
| const int validStart = jlimit (bufferValidStart, bufferValidEnd, nextPlayPos) - nextPlayPos; | |||
| const int validEnd = jlimit (bufferValidStart, bufferValidEnd, nextPlayPos + info.numSamples) - nextPlayPos; | |||
| const int validStart = (int) (jlimit (bufferValidStart, bufferValidEnd, nextPlayPos) - nextPlayPos); | |||
| const int validEnd = (int) (jlimit (bufferValidStart, bufferValidEnd, nextPlayPos + info.numSamples) - nextPlayPos); | |||
| if (validStart == validEnd) | |||
| { | |||
| @@ -24506,14 +24609,14 @@ void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info | |||
| thread->notify(); | |||
| } | |||
| int BufferingAudioSource::getNextReadPosition() const | |||
| int64 BufferingAudioSource::getNextReadPosition() const | |||
| { | |||
| return (source->isLooping() && nextPlayPos > 0) | |||
| ? nextPlayPos % source->getTotalLength() | |||
| : nextPlayPos; | |||
| } | |||
| void BufferingAudioSource::setNextReadPosition (int newPosition) | |||
| void BufferingAudioSource::setNextReadPosition (int64 newPosition) | |||
| { | |||
| const ScopedLock sl (bufferStartPosLock); | |||
| @@ -24527,7 +24630,7 @@ void BufferingAudioSource::setNextReadPosition (int newPosition) | |||
| bool BufferingAudioSource::readNextBufferChunk() | |||
| { | |||
| int newBVS, newBVE, sectionToReadStart, sectionToReadEnd; | |||
| int64 newBVS, newBVE, sectionToReadStart, sectionToReadEnd; | |||
| { | |||
| const ScopedLock sl (bufferStartPosLock); | |||
| @@ -24539,7 +24642,7 @@ bool BufferingAudioSource::readNextBufferChunk() | |||
| bufferValidEnd = 0; | |||
| } | |||
| newBVS = jmax (0, nextPlayPos); | |||
| newBVS = jmax ((int64) 0, nextPlayPos); | |||
| newBVE = newBVS + buffer.getNumSamples() - 4; | |||
| sectionToReadStart = 0; | |||
| sectionToReadEnd = 0; | |||
| @@ -24556,8 +24659,8 @@ bool BufferingAudioSource::readNextBufferChunk() | |||
| bufferValidStart = 0; | |||
| bufferValidEnd = 0; | |||
| } | |||
| else if (abs (newBVS - bufferValidStart) > 512 | |||
| || abs (newBVE - bufferValidEnd) > 512) | |||
| else if (std::abs ((int) (newBVS - bufferValidStart)) > 512 | |||
| || std::abs ((int) (newBVE - bufferValidEnd)) > 512) | |||
| { | |||
| newBVE = jmin (newBVE, bufferValidEnd + maxChunkSize); | |||
| @@ -24577,7 +24680,7 @@ bool BufferingAudioSource::readNextBufferChunk() | |||
| if (bufferIndexStart < bufferIndexEnd) | |||
| { | |||
| readBufferSection (sectionToReadStart, | |||
| sectionToReadEnd - sectionToReadStart, | |||
| (int) (sectionToReadEnd - sectionToReadStart), | |||
| bufferIndexStart); | |||
| } | |||
| else | |||
| @@ -24589,7 +24692,7 @@ bool BufferingAudioSource::readNextBufferChunk() | |||
| bufferIndexStart); | |||
| readBufferSection (sectionToReadStart + initialSize, | |||
| (sectionToReadEnd - sectionToReadStart) - initialSize, | |||
| (int) (sectionToReadEnd - sectionToReadStart) - initialSize, | |||
| 0); | |||
| } | |||
| @@ -24606,7 +24709,7 @@ bool BufferingAudioSource::readNextBufferChunk() | |||
| } | |||
| } | |||
| void BufferingAudioSource::readBufferSection (int start, int length, int bufferOffset) | |||
| void BufferingAudioSource::readBufferSection (const int64 start, const int length, const int bufferOffset) | |||
| { | |||
| if (source->getNextReadPosition() != start) | |||
| source->setNextReadPosition (start); | |||
| @@ -27565,7 +27668,7 @@ float AudioSampleBuffer::getRMSLevel (const int channel, | |||
| void AudioSampleBuffer::readFromAudioReader (AudioFormatReader* reader, | |||
| const int startSample, | |||
| const int numSamples, | |||
| const int readerStartSample, | |||
| const int64 readerStartSample, | |||
| const bool useLeftChan, | |||
| const bool useRightChan) | |||
| { | |||
| @@ -5962,6 +5962,9 @@ public: | |||
| */ | |||
| virtual void writeDoubleBigEndian (double value); | |||
| /** Writes a byte to the output stream a given number of times. */ | |||
| virtual void writeRepeatedByte (uint8 byte, int numTimesToRepeat); | |||
| /** Writes a condensed binary encoding of a 32-bit integer. | |||
| If you're storing a lot of integers which are unlikely to have very large values, | |||
| @@ -32536,7 +32539,7 @@ public: | |||
| void readFromAudioReader (AudioFormatReader* reader, | |||
| int startSample, | |||
| int numSamples, | |||
| int readerStartSample, | |||
| int64 readerStartSample, | |||
| bool useReaderLeftChan, | |||
| bool useReaderRightChan); | |||
| @@ -34174,16 +34177,16 @@ public: | |||
| Note that this may be called on a different thread to getNextAudioBlock(), | |||
| so the subclass should make sure it's synchronised. | |||
| */ | |||
| virtual void setNextReadPosition (int newPosition) = 0; | |||
| virtual void setNextReadPosition (int64 newPosition) = 0; | |||
| /** Returns the position from which the next block will be returned. | |||
| @see setNextReadPosition | |||
| */ | |||
| virtual int getNextReadPosition() const = 0; | |||
| virtual int64 getNextReadPosition() const = 0; | |||
| /** Returns the total length of the stream (in samples). */ | |||
| virtual int getTotalLength() const = 0; | |||
| virtual int64 getTotalLength() const = 0; | |||
| /** Returns true if this source is actually playing in a loop. */ | |||
| virtual bool isLooping() const = 0; | |||
| @@ -34242,20 +34245,20 @@ public: | |||
| void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill); | |||
| /** Implements the PositionableAudioSource method. */ | |||
| void setNextReadPosition (int newPosition); | |||
| void setNextReadPosition (int64 newPosition); | |||
| /** Implements the PositionableAudioSource method. */ | |||
| int getNextReadPosition() const; | |||
| int64 getNextReadPosition() const; | |||
| /** Implements the PositionableAudioSource method. */ | |||
| int getTotalLength() const; | |||
| int64 getTotalLength() const; | |||
| private: | |||
| AudioFormatReader* reader; | |||
| bool deleteReader; | |||
| int volatile nextPlayPos; | |||
| int64 volatile nextPlayPos; | |||
| bool volatile looping; | |||
| void readBufferSection (int start, int length, AudioSampleBuffer& buffer, int startSample); | |||
| @@ -34707,13 +34710,13 @@ public: | |||
| void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill); | |||
| /** Implements the PositionableAudioSource method. */ | |||
| void setNextReadPosition (int newPosition); | |||
| void setNextReadPosition (int64 newPosition); | |||
| /** Implements the PositionableAudioSource method. */ | |||
| int getNextReadPosition() const; | |||
| int64 getNextReadPosition() const; | |||
| /** Implements the PositionableAudioSource method. */ | |||
| int getTotalLength() const { return source->getTotalLength(); } | |||
| int64 getTotalLength() const { return source->getTotalLength(); } | |||
| /** Implements the PositionableAudioSource method. */ | |||
| bool isLooping() const { return source->isLooping(); } | |||
| @@ -34725,13 +34728,13 @@ private: | |||
| int numberOfSamplesToBuffer; | |||
| AudioSampleBuffer buffer; | |||
| CriticalSection bufferStartPosLock; | |||
| int volatile bufferValidStart, bufferValidEnd, nextPlayPos; | |||
| int64 volatile bufferValidStart, bufferValidEnd, nextPlayPos; | |||
| bool wasSourceLooping; | |||
| double volatile sampleRate; | |||
| friend class SharedBufferingAudioSourceThread; | |||
| bool readNextBufferChunk(); | |||
| void readBufferSection (int start, int length, int bufferOffset); | |||
| void readBufferSection (int64 start, int length, int bufferOffset); | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BufferingAudioSource); | |||
| }; | |||
| @@ -34928,13 +34931,13 @@ public: | |||
| void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill); | |||
| /** Implements the PositionableAudioSource method. */ | |||
| void setNextReadPosition (int newPosition); | |||
| void setNextReadPosition (int64 newPosition); | |||
| /** Implements the PositionableAudioSource method. */ | |||
| int getNextReadPosition() const; | |||
| int64 getNextReadPosition() const; | |||
| /** Implements the PositionableAudioSource method. */ | |||
| int getTotalLength() const; | |||
| int64 getTotalLength() const; | |||
| /** Implements the PositionableAudioSource method. */ | |||
| bool isLooping() const; | |||
| @@ -144,10 +144,6 @@ public: | |||
| } | |||
| } | |||
| ~AiffAudioFormatReader() | |||
| { | |||
| } | |||
| //============================================================================== | |||
| bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | |||
| int64 startSampleInFile, int numSamples) | |||
| @@ -69,6 +69,9 @@ const StringPairArray WavAudioFormat::createBWAVMetadata (const String& descript | |||
| //============================================================================== | |||
| namespace WavFileHelpers | |||
| { | |||
| #if JUCE_MSVC | |||
| #pragma pack (push, 1) | |||
| #define PACKED | |||
| @@ -246,6 +249,17 @@ struct ExtensibleWavSubFormat | |||
| uint8 data4[8]; | |||
| } PACKED; | |||
| struct DataSize64Chunk // chunk ID = 'ds64' if data size > 0xffffffff, 'JUNK' otherwise | |||
| { | |||
| uint32 riffSizeLow; // low 4 byte size of RF64 block | |||
| uint32 riffSizeHigh; // high 4 byte size of RF64 block | |||
| uint32 dataSizeLow; // low 4 byte size of data chunk | |||
| uint32 dataSizeHigh; // high 4 byte size of data chunk | |||
| uint32 sampleCountLow; // low 4 byte sample count of fact chunk | |||
| uint32 sampleCountHigh; // high 4 byte sample count of fact chunk | |||
| uint32 tableLength; // number of valid entries in array 'table' | |||
| } PACKED; | |||
| #if JUCE_MSVC | |||
| #pragma pack (pop) | |||
| @@ -253,6 +267,9 @@ struct ExtensibleWavSubFormat | |||
| #undef PACKED | |||
| inline int chunkName (const char* const name) { return (int) ByteOrder::littleEndianInt (name); } | |||
| } | |||
| //============================================================================== | |||
| class WavAudioFormatReader : public AudioFormatReader | |||
| @@ -263,109 +280,146 @@ public: | |||
| : AudioFormatReader (in, TRANS (wavFormatName)), | |||
| bwavChunkStart (0), | |||
| bwavSize (0), | |||
| dataLength (0) | |||
| dataLength (0), | |||
| isRF64 (false) | |||
| { | |||
| if (input->readInt() == chunkName ("RIFF")) | |||
| using namespace WavFileHelpers; | |||
| uint64 len = 0; | |||
| int64 end = 0; | |||
| bool hasGotType = false; | |||
| bool hasGotData = false; | |||
| const int firstChunkType = input->readInt(); | |||
| if (firstChunkType == chunkName ("RF64")) | |||
| { | |||
| input->skipNextBytes (4); // size is -1 for RF64 | |||
| isRF64 = true; | |||
| } | |||
| else if (firstChunkType == chunkName ("RIFF")) | |||
| { | |||
| const uint32 len = (uint32) input->readInt(); | |||
| const int64 end = input->getPosition() + len; | |||
| bool hasGotType = false; | |||
| bool hasGotData = false; | |||
| len = (uint64) input->readInt(); | |||
| end = input->getPosition() + len; | |||
| } | |||
| else | |||
| { | |||
| return; | |||
| } | |||
| const int64 startOfRIFFChunk = input->getPosition(); | |||
| if (input->readInt() == chunkName ("WAVE")) | |||
| if (input->readInt() == chunkName ("WAVE")) | |||
| { | |||
| if (isRF64 && input->readInt() == chunkName ("ds64")) | |||
| { | |||
| while (input->getPosition() < end | |||
| && ! input->isExhausted()) | |||
| uint32 length = (uint32) input->readInt(); | |||
| if (length < 28) | |||
| { | |||
| return; | |||
| } | |||
| else | |||
| { | |||
| const int chunkType = input->readInt(); | |||
| uint32 length = (uint32) input->readInt(); | |||
| const int64 chunkEnd = input->getPosition() + length + (length & 1); | |||
| len = input->readInt64(); | |||
| end = startOfRIFFChunk + len; | |||
| dataLength = input->readInt64(); | |||
| input->setPosition (chunkEnd); | |||
| } | |||
| } | |||
| if (chunkType == chunkName ("fmt ")) | |||
| { | |||
| // read the format chunk | |||
| const unsigned short format = input->readShort(); | |||
| const short numChans = input->readShort(); | |||
| sampleRate = input->readInt(); | |||
| const int bytesPerSec = input->readInt(); | |||
| while (input->getPosition() < end && ! input->isExhausted()) | |||
| { | |||
| const int chunkType = input->readInt(); | |||
| uint32 length = (uint32) input->readInt(); | |||
| const int64 chunkEnd = input->getPosition() + length + (length & 1); | |||
| if (chunkType == chunkName ("fmt ")) | |||
| { | |||
| // read the format chunk | |||
| const unsigned short format = input->readShort(); | |||
| const short numChans = input->readShort(); | |||
| sampleRate = input->readInt(); | |||
| const int bytesPerSec = input->readInt(); | |||
| numChannels = numChans; | |||
| bytesPerFrame = bytesPerSec / (int)sampleRate; | |||
| bitsPerSample = 8 * bytesPerFrame / numChans; | |||
| numChannels = numChans; | |||
| bytesPerFrame = bytesPerSec / (int)sampleRate; | |||
| bitsPerSample = 8 * bytesPerFrame / numChans; | |||
| if (format == 3) | |||
| if (format == 3) | |||
| { | |||
| usesFloatingPointData = true; | |||
| } | |||
| else if (format == 0xfffe /*WAVE_FORMAT_EXTENSIBLE*/) | |||
| { | |||
| if (length < 40) // too short | |||
| { | |||
| usesFloatingPointData = true; | |||
| bytesPerFrame = 0; | |||
| } | |||
| else if (format == 0xfffe /*WAVE_FORMAT_EXTENSIBLE*/) | |||
| else | |||
| { | |||
| if (length < 40) // too short | |||
| { | |||
| bytesPerFrame = 0; | |||
| } | |||
| else | |||
| input->skipNextBytes (12); // skip over blockAlign, bitsPerSample and speakerPosition mask | |||
| ExtensibleWavSubFormat subFormat; | |||
| subFormat.data1 = input->readInt(); | |||
| subFormat.data2 = input->readShort(); | |||
| subFormat.data3 = input->readShort(); | |||
| input->read (subFormat.data4, sizeof (subFormat.data4)); | |||
| const ExtensibleWavSubFormat pcmFormat | |||
| = { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; | |||
| if (memcmp (&subFormat, &pcmFormat, sizeof (subFormat)) != 0) | |||
| { | |||
| input->skipNextBytes (12); // skip over blockAlign, bitsPerSample and speakerPosition mask | |||
| ExtensibleWavSubFormat subFormat; | |||
| subFormat.data1 = input->readInt(); | |||
| subFormat.data2 = input->readShort(); | |||
| subFormat.data3 = input->readShort(); | |||
| input->read (subFormat.data4, sizeof (subFormat.data4)); | |||
| const ExtensibleWavSubFormat pcmFormat | |||
| = { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; | |||
| if (memcmp (&subFormat, &pcmFormat, sizeof (subFormat)) != 0) | |||
| { | |||
| const ExtensibleWavSubFormat ambisonicFormat | |||
| = { 0x00000001, 0x0721, 0x11d3, { 0x86, 0x44, 0xC8, 0xC1, 0xCA, 0x00, 0x00, 0x00 } }; | |||
| if (memcmp (&subFormat, &ambisonicFormat, sizeof (subFormat)) != 0) | |||
| bytesPerFrame = 0; | |||
| } | |||
| const ExtensibleWavSubFormat ambisonicFormat | |||
| = { 0x00000001, 0x0721, 0x11d3, { 0x86, 0x44, 0xC8, 0xC1, 0xCA, 0x00, 0x00, 0x00 } }; | |||
| if (memcmp (&subFormat, &ambisonicFormat, sizeof (subFormat)) != 0) | |||
| bytesPerFrame = 0; | |||
| } | |||
| } | |||
| else if (format != 1) | |||
| { | |||
| bytesPerFrame = 0; | |||
| } | |||
| hasGotType = true; | |||
| } | |||
| else if (chunkType == chunkName ("data")) | |||
| else if (format != 1) | |||
| { | |||
| // get the data chunk's position | |||
| bytesPerFrame = 0; | |||
| } | |||
| hasGotType = true; | |||
| } | |||
| else if (chunkType == chunkName ("data")) | |||
| { | |||
| // get the data chunk's position | |||
| if (! isRF64) // data size is expected to be -1, actual data size is in ds64 chunk | |||
| dataLength = length; | |||
| dataChunkStart = input->getPosition(); | |||
| lengthInSamples = (bytesPerFrame > 0) ? (dataLength / bytesPerFrame) : 0; | |||
| hasGotData = true; | |||
| } | |||
| else if (chunkType == chunkName ("bext")) | |||
| { | |||
| bwavChunkStart = input->getPosition(); | |||
| bwavSize = length; | |||
| // Broadcast-wav extension chunk.. | |||
| HeapBlock <BWAVChunk> bwav; | |||
| bwav.calloc (jmax ((size_t) length + 1, sizeof (BWAVChunk)), 1); | |||
| input->read (bwav, length); | |||
| bwav->copyTo (metadataValues); | |||
| } | |||
| else if (chunkType == chunkName ("smpl")) | |||
| { | |||
| HeapBlock <SMPLChunk> smpl; | |||
| smpl.calloc (jmax ((size_t) length + 1, sizeof (SMPLChunk)), 1); | |||
| input->read (smpl, length); | |||
| smpl->copyTo (metadataValues, length); | |||
| } | |||
| else if (chunkEnd <= input->getPosition()) | |||
| { | |||
| break; | |||
| } | |||
| dataChunkStart = input->getPosition(); | |||
| lengthInSamples = (bytesPerFrame > 0) ? (dataLength / bytesPerFrame) : 0; | |||
| input->setPosition (chunkEnd); | |||
| hasGotData = true; | |||
| } | |||
| else if (chunkType == chunkName ("bext")) | |||
| { | |||
| bwavChunkStart = input->getPosition(); | |||
| bwavSize = length; | |||
| // Broadcast-wav extension chunk.. | |||
| HeapBlock <BWAVChunk> bwav; | |||
| bwav.calloc (jmax ((size_t) length + 1, sizeof (BWAVChunk)), 1); | |||
| input->read (bwav, length); | |||
| bwav->copyTo (metadataValues); | |||
| } | |||
| else if (chunkType == chunkName ("smpl")) | |||
| { | |||
| HeapBlock <SMPLChunk> smpl; | |||
| smpl.calloc (jmax ((size_t) length + 1, sizeof (SMPLChunk)), 1); | |||
| input->read (smpl, length); | |||
| smpl->copyTo (metadataValues, length); | |||
| } | |||
| else if (chunkEnd <= input->getPosition()) | |||
| { | |||
| break; | |||
| } | |||
| input->setPosition (chunkEnd); | |||
| } | |||
| } | |||
| } | |||
| @@ -428,8 +482,7 @@ private: | |||
| ScopedPointer<AudioData::Converter> converter; | |||
| int bytesPerFrame; | |||
| int64 dataChunkStart, dataLength; | |||
| static inline int chunkName (const char* const name) { return (int) ByteOrder::littleEndianInt (name); } | |||
| bool isRF64; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatReader); | |||
| }; | |||
| @@ -439,20 +492,17 @@ class WavAudioFormatWriter : public AudioFormatWriter | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| WavAudioFormatWriter (OutputStream* const out, | |||
| const double sampleRate_, | |||
| const unsigned int numChannels_, | |||
| const int bits, | |||
| WavAudioFormatWriter (OutputStream* const out, const double sampleRate_, | |||
| const unsigned int numChannels_, const int bits, | |||
| const StringPairArray& metadataValues) | |||
| : AudioFormatWriter (out, | |||
| TRANS (wavFormatName), | |||
| sampleRate_, | |||
| numChannels_, | |||
| bits), | |||
| : AudioFormatWriter (out, TRANS (wavFormatName), sampleRate_, numChannels_, bits), | |||
| lengthInSamples (0), | |||
| bytesWritten (0), | |||
| writeFailed (false) | |||
| writeFailed (false), | |||
| isRF64 (false) | |||
| { | |||
| using namespace WavFileHelpers; | |||
| if (metadataValues.size() > 0) | |||
| { | |||
| bwavChunk = BWAVChunk::createFrom (metadataValues); | |||
| @@ -465,6 +515,12 @@ public: | |||
| ~WavAudioFormatWriter() | |||
| { | |||
| if ((bytesWritten & 1) != 0) // pad to an even length | |||
| { | |||
| ++bytesWritten; | |||
| output->writeByte (0); | |||
| } | |||
| writeHeader(); | |||
| } | |||
| @@ -488,8 +544,7 @@ public: | |||
| default: jassertfalse; break; | |||
| } | |||
| if (bytesWritten + bytes >= (uint32) 0xfff00000 | |||
| || ! output->write (tempBlock.getData(), bytes)) | |||
| if (! output->write (tempBlock.getData(), bytes)) | |||
| { | |||
| // failed to write to disk, so let's try writing the header. | |||
| // If it's just run out of disk space, then if it does manage | |||
| @@ -510,14 +565,27 @@ public: | |||
| private: | |||
| ScopedPointer<AudioData::Converter> converter; | |||
| MemoryBlock tempBlock, bwavChunk, smplChunk; | |||
| uint32 lengthInSamples, bytesWritten; | |||
| uint64 lengthInSamples, bytesWritten; | |||
| int64 headerPosition; | |||
| bool writeFailed; | |||
| bool writeFailed, isRF64; | |||
| static int getChannelMask (const int numChannels) throw() | |||
| { | |||
| switch (numChannels) | |||
| { | |||
| case 1: return 0; | |||
| case 2: return 1 + 2; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | |||
| case 5: return 1 + 2 + 4 + 16 + 32; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | |||
| case 6: return 1 + 2 + 4 + 8 + 16 + 32; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | |||
| default: break; | |||
| } | |||
| static inline int chunkName (const char* const name) { return (int) ByteOrder::littleEndianInt (name); } | |||
| return 0; | |||
| } | |||
| void writeHeader() | |||
| { | |||
| using namespace WavFileHelpers; | |||
| const bool seekedOk = output->setPosition (headerPosition); | |||
| (void) seekedOk; | |||
| @@ -526,21 +594,62 @@ private: | |||
| jassert (seekedOk); | |||
| const int bytesPerFrame = numChannels * bitsPerSample / 8; | |||
| output->writeInt (chunkName ("RIFF")); | |||
| output->writeInt ((int) (lengthInSamples * bytesPerFrame | |||
| + ((bwavChunk.getSize() > 0) ? (44 + bwavChunk.getSize()) : 36) | |||
| + (smplChunk.getSize() > 0 ? smplChunk.getSize() + 8 : 0))); | |||
| int64 audioDataSize = bytesPerFrame * lengthInSamples; | |||
| int64 riffChunkSize = 4 /* 'WAVE' */ + 8 + 40 /* WAVEFORMATEX */ | |||
| + 8 + audioDataSize + (audioDataSize & 1) | |||
| + (bwavChunk.getSize() > 0 ? (8 + bwavChunk.getSize()) : 0) | |||
| + (smplChunk.getSize() > 0 ? (8 + smplChunk.getSize()) : 0) | |||
| + (8 + 28); // (JUNK chunk) | |||
| riffChunkSize += (riffChunkSize & 0x1); | |||
| isRF64 = (riffChunkSize > 0xffffffff); | |||
| output->writeInt (chunkName (isRF64 ? "RF64" : "RIFF")); | |||
| output->writeInt (isRF64 ? -1 : (int) riffChunkSize); | |||
| output->writeInt (chunkName ("WAVE")); | |||
| if (! isRF64) | |||
| { | |||
| // write Junk chunk | |||
| output->writeInt (chunkName ("JUNK")); | |||
| output->writeInt (28); | |||
| output->writeRepeatedByte (0, 28); | |||
| } | |||
| else | |||
| { | |||
| // write ds64 chunk | |||
| output->writeInt (chunkName ("ds64")); | |||
| output->writeInt (28); // chunk size for uncompressed data (no table) | |||
| output->writeInt64 (riffChunkSize); | |||
| output->writeInt64 (audioDataSize); | |||
| output->writeRepeatedByte (0, 12); | |||
| } | |||
| output->writeInt (chunkName ("fmt ")); | |||
| output->writeInt (16); | |||
| output->writeShort ((bitsPerSample < 32) ? (short) 1 /*WAVE_FORMAT_PCM*/ | |||
| : (short) 3 /*WAVE_FORMAT_IEEE_FLOAT*/); | |||
| output->writeInt (40); // WAVEFORMATEX chunk size | |||
| output->writeShort ((short) (uint16) 0xfffe); // WAVE_FORMAT_EXTENSIBLE | |||
| output->writeShort ((short) numChannels); | |||
| output->writeInt ((int) sampleRate); | |||
| output->writeInt (bytesPerFrame * (int) sampleRate); | |||
| output->writeShort ((short) bytesPerFrame); | |||
| output->writeShort ((short) bitsPerSample); | |||
| output->writeInt ((int) (bytesPerFrame * sampleRate)); // nAvgBytesPerSec | |||
| output->writeShort ((short) bytesPerFrame); // nBlockAlign | |||
| output->writeShort ((short) bitsPerSample); // wBitsPerSample | |||
| output->writeShort (22); // cbSize (size of the extension) | |||
| output->writeShort ((short) bitsPerSample); // wValidBitsPerSample | |||
| output->writeInt (getChannelMask (numChannels)); | |||
| const ExtensibleWavSubFormat pcmFormat | |||
| = { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; | |||
| const ExtensibleWavSubFormat IEEEFloatFormat | |||
| = { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; | |||
| const ExtensibleWavSubFormat& subFormat = bitsPerSample < 32 ? pcmFormat : IEEEFloatFormat; | |||
| output->writeInt ((int) subFormat.data1); | |||
| output->writeShort ((short) subFormat.data2); | |||
| output->writeShort ((short) subFormat.data3); | |||
| output->write (subFormat.data4, sizeof (subFormat.data4)); | |||
| if (bwavChunk.getSize() > 0) | |||
| { | |||
| @@ -557,7 +666,7 @@ private: | |||
| } | |||
| output->writeInt (chunkName ("data")); | |||
| output->writeInt (lengthInSamples * bytesPerFrame); | |||
| output->writeInt (isRF64 ? -1 : (int) (lengthInSamples * bytesPerFrame)); | |||
| usesFloatingPointData = (bitsPerSample == 32); | |||
| } | |||
| @@ -604,28 +713,19 @@ AudioFormatReader* WavAudioFormat::createReaderFor (InputStream* sourceStream, | |||
| return 0; | |||
| } | |||
| AudioFormatWriter* WavAudioFormat::createWriterFor (OutputStream* out, | |||
| double sampleRate, | |||
| unsigned int numChannels, | |||
| int bitsPerSample, | |||
| const StringPairArray& metadataValues, | |||
| int /*qualityOptionIndex*/) | |||
| AudioFormatWriter* WavAudioFormat::createWriterFor (OutputStream* out, double sampleRate, | |||
| unsigned int numChannels, int bitsPerSample, | |||
| const StringPairArray& metadataValues, int /*qualityOptionIndex*/) | |||
| { | |||
| if (getPossibleBitDepths().contains (bitsPerSample)) | |||
| { | |||
| return new WavAudioFormatWriter (out, | |||
| sampleRate, | |||
| numChannels, | |||
| bitsPerSample, | |||
| metadataValues); | |||
| } | |||
| return new WavAudioFormatWriter (out, sampleRate, numChannels, bitsPerSample, metadataValues); | |||
| return 0; | |||
| } | |||
| namespace | |||
| namespace WavFileHelpers | |||
| { | |||
| bool juce_slowCopyOfWavFileWithNewMetadata (const File& file, const StringPairArray& metadata) | |||
| bool slowCopyWavFileWithNewMetadata (const File& file, const StringPairArray& metadata) | |||
| { | |||
| TemporaryFile tempFile (file); | |||
| @@ -661,7 +761,8 @@ namespace | |||
| bool WavAudioFormat::replaceMetadataInFile (const File& wavFile, const StringPairArray& newMetadata) | |||
| { | |||
| ScopedPointer <WavAudioFormatReader> reader ((WavAudioFormatReader*) createReaderFor (wavFile.createInputStream(), true)); | |||
| using namespace WavFileHelpers; | |||
| ScopedPointer <WavAudioFormatReader> reader (static_cast <WavAudioFormatReader*> (createReaderFor (wavFile.createInputStream(), true))); | |||
| if (reader != 0) | |||
| { | |||
| @@ -692,7 +793,7 @@ bool WavAudioFormat::replaceMetadataInFile (const File& wavFile, const StringPai | |||
| } | |||
| } | |||
| return juce_slowCopyOfWavFileWithNewMetadata (wavFile, newMetadata); | |||
| return slowCopyWavFileWithNewMetadata (wavFile, newMetadata); | |||
| } | |||
| @@ -50,7 +50,7 @@ AudioFormatReaderSource::~AudioFormatReaderSource() | |||
| delete reader; | |||
| } | |||
| void AudioFormatReaderSource::setNextReadPosition (int newPosition) | |||
| void AudioFormatReaderSource::setNextReadPosition (int64 newPosition) | |||
| { | |||
| nextPlayPos = newPosition; | |||
| } | |||
| @@ -60,15 +60,15 @@ void AudioFormatReaderSource::setLooping (bool shouldLoop) | |||
| looping = shouldLoop; | |||
| } | |||
| int AudioFormatReaderSource::getNextReadPosition() const | |||
| int64 AudioFormatReaderSource::getNextReadPosition() const | |||
| { | |||
| return (looping) ? (nextPlayPos % (int) reader->lengthInSamples) | |||
| : nextPlayPos; | |||
| return looping ? nextPlayPos % reader->lengthInSamples | |||
| : nextPlayPos; | |||
| } | |||
| int AudioFormatReaderSource::getTotalLength() const | |||
| int64 AudioFormatReaderSource::getTotalLength() const | |||
| { | |||
| return (int) reader->lengthInSamples; | |||
| return reader->lengthInSamples; | |||
| } | |||
| void AudioFormatReaderSource::prepareToPlay (int /*samplesPerBlockExpected*/, | |||
| @@ -84,7 +84,7 @@ void AudioFormatReaderSource::getNextAudioBlock (const AudioSourceChannelInfo& i | |||
| { | |||
| if (info.numSamples > 0) | |||
| { | |||
| const int start = nextPlayPos; | |||
| const int64 start = nextPlayPos; | |||
| if (looping) | |||
| { | |||
| @@ -83,20 +83,20 @@ public: | |||
| //============================================================================== | |||
| /** Implements the PositionableAudioSource method. */ | |||
| void setNextReadPosition (int newPosition); | |||
| void setNextReadPosition (int64 newPosition); | |||
| /** Implements the PositionableAudioSource method. */ | |||
| int getNextReadPosition() const; | |||
| int64 getNextReadPosition() const; | |||
| /** Implements the PositionableAudioSource method. */ | |||
| int getTotalLength() const; | |||
| int64 getTotalLength() const; | |||
| private: | |||
| //============================================================================== | |||
| AudioFormatReader* reader; | |||
| bool deleteReader; | |||
| int volatile nextPlayPos; | |||
| int64 volatile nextPlayPos; | |||
| bool volatile looping; | |||
| void readBufferSection (int start, int length, AudioSampleBuffer& buffer, int startSample); | |||
| @@ -159,7 +159,7 @@ void AudioTransportSource::stop() | |||
| void AudioTransportSource::setPosition (double newPosition) | |||
| { | |||
| if (sampleRate > 0.0) | |||
| setNextReadPosition (roundToInt (newPosition * sampleRate)); | |||
| setNextReadPosition ((int64) (newPosition * sampleRate)); | |||
| } | |||
| double AudioTransportSource::getCurrentPosition() const | |||
| @@ -175,30 +175,30 @@ double AudioTransportSource::getLengthInSeconds() const | |||
| return getTotalLength() / sampleRate; | |||
| } | |||
| void AudioTransportSource::setNextReadPosition (int newPosition) | |||
| void AudioTransportSource::setNextReadPosition (int64 newPosition) | |||
| { | |||
| if (positionableSource != 0) | |||
| { | |||
| if (sampleRate > 0 && sourceSampleRate > 0) | |||
| newPosition = roundToInt (newPosition * sourceSampleRate / sampleRate); | |||
| newPosition = (int64) (newPosition * sourceSampleRate / sampleRate); | |||
| positionableSource->setNextReadPosition (newPosition); | |||
| } | |||
| } | |||
| int AudioTransportSource::getNextReadPosition() const | |||
| int64 AudioTransportSource::getNextReadPosition() const | |||
| { | |||
| if (positionableSource != 0) | |||
| { | |||
| const double ratio = (sampleRate > 0 && sourceSampleRate > 0) ? sampleRate / sourceSampleRate : 1.0; | |||
| return roundToInt (positionableSource->getNextReadPosition() * ratio); | |||
| return (int64) (positionableSource->getNextReadPosition() * ratio); | |||
| } | |||
| return 0; | |||
| } | |||
| int AudioTransportSource::getTotalLength() const | |||
| int64 AudioTransportSource::getTotalLength() const | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| @@ -206,7 +206,7 @@ int AudioTransportSource::getTotalLength() const | |||
| { | |||
| const double ratio = (sampleRate > 0 && sourceSampleRate > 0) ? sampleRate / sourceSampleRate : 1.0; | |||
| return roundToInt (positionableSource->getTotalLength() * ratio); | |||
| return (int64) (positionableSource->getTotalLength() * ratio); | |||
| } | |||
| return 0; | |||
| @@ -148,13 +148,13 @@ public: | |||
| //============================================================================== | |||
| /** Implements the PositionableAudioSource method. */ | |||
| void setNextReadPosition (int newPosition); | |||
| void setNextReadPosition (int64 newPosition); | |||
| /** Implements the PositionableAudioSource method. */ | |||
| int getNextReadPosition() const; | |||
| int64 getNextReadPosition() const; | |||
| /** Implements the PositionableAudioSource method. */ | |||
| int getTotalLength() const; | |||
| int64 getTotalLength() const; | |||
| /** Implements the PositionableAudioSource method. */ | |||
| bool isLooping() const; | |||
| @@ -187,8 +187,8 @@ void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info | |||
| { | |||
| const ScopedLock sl (bufferStartPosLock); | |||
| const int validStart = jlimit (bufferValidStart, bufferValidEnd, nextPlayPos) - nextPlayPos; | |||
| const int validEnd = jlimit (bufferValidStart, bufferValidEnd, nextPlayPos + info.numSamples) - nextPlayPos; | |||
| const int validStart = (int) (jlimit (bufferValidStart, bufferValidEnd, nextPlayPos) - nextPlayPos); | |||
| const int validEnd = (int) (jlimit (bufferValidStart, bufferValidEnd, nextPlayPos + info.numSamples) - nextPlayPos); | |||
| if (validStart == validEnd) | |||
| { | |||
| @@ -247,14 +247,14 @@ void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info | |||
| thread->notify(); | |||
| } | |||
| int BufferingAudioSource::getNextReadPosition() const | |||
| int64 BufferingAudioSource::getNextReadPosition() const | |||
| { | |||
| return (source->isLooping() && nextPlayPos > 0) | |||
| ? nextPlayPos % source->getTotalLength() | |||
| : nextPlayPos; | |||
| } | |||
| void BufferingAudioSource::setNextReadPosition (int newPosition) | |||
| void BufferingAudioSource::setNextReadPosition (int64 newPosition) | |||
| { | |||
| const ScopedLock sl (bufferStartPosLock); | |||
| @@ -268,7 +268,7 @@ void BufferingAudioSource::setNextReadPosition (int newPosition) | |||
| bool BufferingAudioSource::readNextBufferChunk() | |||
| { | |||
| int newBVS, newBVE, sectionToReadStart, sectionToReadEnd; | |||
| int64 newBVS, newBVE, sectionToReadStart, sectionToReadEnd; | |||
| { | |||
| const ScopedLock sl (bufferStartPosLock); | |||
| @@ -280,7 +280,7 @@ bool BufferingAudioSource::readNextBufferChunk() | |||
| bufferValidEnd = 0; | |||
| } | |||
| newBVS = jmax (0, nextPlayPos); | |||
| newBVS = jmax ((int64) 0, nextPlayPos); | |||
| newBVE = newBVS + buffer.getNumSamples() - 4; | |||
| sectionToReadStart = 0; | |||
| sectionToReadEnd = 0; | |||
| @@ -297,8 +297,8 @@ bool BufferingAudioSource::readNextBufferChunk() | |||
| bufferValidStart = 0; | |||
| bufferValidEnd = 0; | |||
| } | |||
| else if (abs (newBVS - bufferValidStart) > 512 | |||
| || abs (newBVE - bufferValidEnd) > 512) | |||
| else if (std::abs ((int) (newBVS - bufferValidStart)) > 512 | |||
| || std::abs ((int) (newBVE - bufferValidEnd)) > 512) | |||
| { | |||
| newBVE = jmin (newBVE, bufferValidEnd + maxChunkSize); | |||
| @@ -318,7 +318,7 @@ bool BufferingAudioSource::readNextBufferChunk() | |||
| if (bufferIndexStart < bufferIndexEnd) | |||
| { | |||
| readBufferSection (sectionToReadStart, | |||
| sectionToReadEnd - sectionToReadStart, | |||
| (int) (sectionToReadEnd - sectionToReadStart), | |||
| bufferIndexStart); | |||
| } | |||
| else | |||
| @@ -330,7 +330,7 @@ bool BufferingAudioSource::readNextBufferChunk() | |||
| bufferIndexStart); | |||
| readBufferSection (sectionToReadStart + initialSize, | |||
| (sectionToReadEnd - sectionToReadStart) - initialSize, | |||
| (int) (sectionToReadEnd - sectionToReadStart) - initialSize, | |||
| 0); | |||
| } | |||
| @@ -347,7 +347,7 @@ bool BufferingAudioSource::readNextBufferChunk() | |||
| } | |||
| } | |||
| void BufferingAudioSource::readBufferSection (int start, int length, int bufferOffset) | |||
| void BufferingAudioSource::readBufferSection (const int64 start, const int length, const int bufferOffset) | |||
| { | |||
| if (source->getNextReadPosition() != start) | |||
| source->setNextReadPosition (start); | |||
| @@ -75,13 +75,13 @@ public: | |||
| //============================================================================== | |||
| /** Implements the PositionableAudioSource method. */ | |||
| void setNextReadPosition (int newPosition); | |||
| void setNextReadPosition (int64 newPosition); | |||
| /** Implements the PositionableAudioSource method. */ | |||
| int getNextReadPosition() const; | |||
| int64 getNextReadPosition() const; | |||
| /** Implements the PositionableAudioSource method. */ | |||
| int getTotalLength() const { return source->getTotalLength(); } | |||
| int64 getTotalLength() const { return source->getTotalLength(); } | |||
| /** Implements the PositionableAudioSource method. */ | |||
| bool isLooping() const { return source->isLooping(); } | |||
| @@ -93,13 +93,13 @@ private: | |||
| int numberOfSamplesToBuffer; | |||
| AudioSampleBuffer buffer; | |||
| CriticalSection bufferStartPosLock; | |||
| int volatile bufferValidStart, bufferValidEnd, nextPlayPos; | |||
| int64 volatile bufferValidStart, bufferValidEnd, nextPlayPos; | |||
| bool wasSourceLooping; | |||
| double volatile sampleRate; | |||
| friend class SharedBufferingAudioSourceThread; | |||
| bool readNextBufferChunk(); | |||
| void readBufferSection (int start, int length, int bufferOffset); | |||
| void readBufferSection (int64 start, int length, int bufferOffset); | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BufferingAudioSource); | |||
| }; | |||
| @@ -59,16 +59,16 @@ public: | |||
| Note that this may be called on a different thread to getNextAudioBlock(), | |||
| so the subclass should make sure it's synchronised. | |||
| */ | |||
| virtual void setNextReadPosition (int newPosition) = 0; | |||
| virtual void setNextReadPosition (int64 newPosition) = 0; | |||
| /** Returns the position from which the next block will be returned. | |||
| @see setNextReadPosition | |||
| */ | |||
| virtual int getNextReadPosition() const = 0; | |||
| virtual int64 getNextReadPosition() const = 0; | |||
| /** Returns the total length of the stream (in samples). */ | |||
| virtual int getTotalLength() const = 0; | |||
| virtual int64 getTotalLength() const = 0; | |||
| /** Returns true if this source is actually playing in a loop. */ | |||
| virtual bool isLooping() const = 0; | |||
| @@ -547,7 +547,7 @@ float AudioSampleBuffer::getRMSLevel (const int channel, | |||
| void AudioSampleBuffer::readFromAudioReader (AudioFormatReader* reader, | |||
| const int startSample, | |||
| const int numSamples, | |||
| const int readerStartSample, | |||
| const int64 readerStartSample, | |||
| const bool useLeftChan, | |||
| const bool useRightChan) | |||
| { | |||
| @@ -405,7 +405,7 @@ public: | |||
| void readFromAudioReader (AudioFormatReader* reader, | |||
| int startSample, | |||
| int numSamples, | |||
| int readerStartSample, | |||
| int64 readerStartSample, | |||
| bool useReaderLeftChan, | |||
| bool useReaderRightChan); | |||
| @@ -78,6 +78,12 @@ void OutputStream::writeByte (char byte) | |||
| write (&byte, 1); | |||
| } | |||
| void OutputStream::writeRepeatedByte (uint8 byte, int numTimesToRepeat) | |||
| { | |||
| while (--numTimesToRepeat >= 0) | |||
| writeByte (byte); | |||
| } | |||
| void OutputStream::writeShort (short value) | |||
| { | |||
| const unsigned short v = ByteOrder::swapIfBigEndian ((unsigned short) value); | |||
| @@ -155,6 +155,9 @@ public: | |||
| */ | |||
| virtual void writeDoubleBigEndian (double value); | |||
| /** Writes a byte to the output stream a given number of times. */ | |||
| virtual void writeRepeatedByte (uint8 byte, int numTimesToRepeat); | |||
| /** Writes a condensed binary encoding of a 32-bit integer. | |||
| If you're storing a lot of integers which are unlikely to have very large values, | |||