- Library code update
- project update (added Eigen) git-svn-id: http://moon:8086/svn/software/trunk/projects/mpsk_rx_gui@94 b431acfa-c32f-4a4a-93f1-934dc6c82436
This commit is contained in:
@@ -37,7 +37,7 @@ const char* const AiffAudioFormat::appleKey = "apple key";
|
||||
//==============================================================================
|
||||
namespace AiffFileHelpers
|
||||
{
|
||||
inline int chunkName (const char* const name) { return (int) ByteOrder::littleEndianInt (name); }
|
||||
inline int chunkName (const char* name) noexcept { return (int) ByteOrder::littleEndianInt (name); }
|
||||
|
||||
#if JUCE_MSVC
|
||||
#pragma pack (push, 1)
|
||||
@@ -888,13 +888,13 @@ AiffAudioFormat::~AiffAudioFormat()
|
||||
Array<int> AiffAudioFormat::getPossibleSampleRates()
|
||||
{
|
||||
const int rates[] = { 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000, 0 };
|
||||
return Array <int> (rates);
|
||||
return Array<int> (rates);
|
||||
}
|
||||
|
||||
Array<int> AiffAudioFormat::getPossibleBitDepths()
|
||||
{
|
||||
const int depths[] = { 8, 16, 24, 0 };
|
||||
return Array <int> (depths);
|
||||
return Array<int> (depths);
|
||||
}
|
||||
|
||||
bool AiffAudioFormat::canDoStereo() { return true; }
|
||||
|
||||
@@ -165,13 +165,13 @@ bool LAMEEncoderAudioFormat::canHandleFile (const File&)
|
||||
Array<int> LAMEEncoderAudioFormat::getPossibleSampleRates()
|
||||
{
|
||||
const int rates[] = { 32000, 44100, 48000, 0 };
|
||||
return Array <int> (rates);
|
||||
return Array<int> (rates);
|
||||
}
|
||||
|
||||
Array<int> LAMEEncoderAudioFormat::getPossibleBitDepths()
|
||||
{
|
||||
const int depths[] = { 16, 0 };
|
||||
return Array <int> (depths);
|
||||
return Array<int> (depths);
|
||||
}
|
||||
|
||||
bool LAMEEncoderAudioFormat::canDoStereo() { return true; }
|
||||
|
||||
@@ -65,6 +65,7 @@ const char* const WavAudioFormat::acidNumerator = "acid numerator";
|
||||
const char* const WavAudioFormat::acidTempo = "acid tempo";
|
||||
|
||||
const char* const WavAudioFormat::ISRC = "ISRC";
|
||||
const char* const WavAudioFormat::tracktionLoopInfo = "tracktion loop info";
|
||||
|
||||
//==============================================================================
|
||||
namespace WavFileHelpers
|
||||
@@ -471,6 +472,46 @@ namespace WavFileHelpers
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
namespace ListInfoChunk
|
||||
{
|
||||
static bool writeValue (const StringPairArray& values, MemoryOutputStream& out, const char* paramName)
|
||||
{
|
||||
const String value (values.getValue (paramName, String()));
|
||||
|
||||
if (value.isEmpty())
|
||||
return false;
|
||||
|
||||
const int valueLength = (int) value.getNumBytesAsUTF8() + 1;
|
||||
const int chunkLength = valueLength + (valueLength & 1);
|
||||
|
||||
out.writeInt (chunkName (paramName));
|
||||
out.writeInt (chunkLength);
|
||||
out.write (value.toUTF8(), (size_t) valueLength);
|
||||
|
||||
if ((out.getDataSize() & 1) != 0)
|
||||
out.writeByte (0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static MemoryBlock createFrom (const StringPairArray& values)
|
||||
{
|
||||
static const char* params[] = { "INAM", "IART", "IPRD", "IPRT", "ISFT",
|
||||
"ISRC", "IGNR", "ICMT", "ICOP", "ICRD" };
|
||||
|
||||
MemoryOutputStream out;
|
||||
out.writeInt (chunkName ("INFO"));
|
||||
bool anyParamsDefined = false;
|
||||
|
||||
for (int i = 0; i < numElementsInArray (params); ++i)
|
||||
if (writeValue (values, out, params[i]))
|
||||
anyParamsDefined = true;
|
||||
|
||||
return anyParamsDefined ? out.getMemoryBlock() : MemoryBlock();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct AcidChunk
|
||||
{
|
||||
@@ -481,6 +522,38 @@ namespace WavFileHelpers
|
||||
input.read (this, (int) jmin (sizeof (*this), length));
|
||||
}
|
||||
|
||||
AcidChunk (const StringPairArray& values)
|
||||
{
|
||||
zerostruct (*this);
|
||||
|
||||
flags = getFlagIfPresent (values, WavAudioFormat::acidOneShot, 0x01)
|
||||
| getFlagIfPresent (values, WavAudioFormat::acidRootSet, 0x02)
|
||||
| getFlagIfPresent (values, WavAudioFormat::acidStretch, 0x04)
|
||||
| getFlagIfPresent (values, WavAudioFormat::acidDiskBased, 0x08)
|
||||
| getFlagIfPresent (values, WavAudioFormat::acidizerFlag, 0x10);
|
||||
|
||||
if (values[WavAudioFormat::acidRootSet].getIntValue() != 0)
|
||||
rootNote = ByteOrder::swapIfBigEndian ((uint16) values[WavAudioFormat::acidRootNote].getIntValue());
|
||||
|
||||
numBeats = ByteOrder::swapIfBigEndian ((uint32) values[WavAudioFormat::acidBeats].getIntValue());
|
||||
meterDenominator = ByteOrder::swapIfBigEndian ((uint16) values[WavAudioFormat::acidDenominator].getIntValue());
|
||||
meterNumerator = ByteOrder::swapIfBigEndian ((uint16) values[WavAudioFormat::acidNumerator].getIntValue());
|
||||
|
||||
if (values.containsKey (WavAudioFormat::acidTempo))
|
||||
tempo = swapFloatByteOrder (values[WavAudioFormat::acidTempo].getFloatValue());
|
||||
}
|
||||
|
||||
static MemoryBlock createFrom (const StringPairArray& values)
|
||||
{
|
||||
return AcidChunk (values).toMemoryBlock();
|
||||
}
|
||||
|
||||
MemoryBlock toMemoryBlock() const
|
||||
{
|
||||
return (flags != 0 || rootNote != 0 || numBeats != 0 || meterDenominator != 0 || meterNumerator != 0)
|
||||
? MemoryBlock (this, sizeof (*this)) : MemoryBlock();
|
||||
}
|
||||
|
||||
void addToMetadata (StringPairArray& values) const
|
||||
{
|
||||
setBoolFlag (values, WavAudioFormat::acidOneShot, 0x01);
|
||||
@@ -490,30 +563,65 @@ namespace WavFileHelpers
|
||||
setBoolFlag (values, WavAudioFormat::acidizerFlag, 0x10);
|
||||
|
||||
if (flags & 0x02) // root note set
|
||||
values.set (WavAudioFormat::acidRootNote, String (rootNote));
|
||||
values.set (WavAudioFormat::acidRootNote, String (ByteOrder::swapIfBigEndian (rootNote)));
|
||||
|
||||
values.set (WavAudioFormat::acidBeats, String (numBeats));
|
||||
values.set (WavAudioFormat::acidDenominator, String (meterDenominator));
|
||||
values.set (WavAudioFormat::acidNumerator, String (meterNumerator));
|
||||
values.set (WavAudioFormat::acidTempo, String (tempo));
|
||||
values.set (WavAudioFormat::acidBeats, String (ByteOrder::swapIfBigEndian (numBeats)));
|
||||
values.set (WavAudioFormat::acidDenominator, String (ByteOrder::swapIfBigEndian (meterDenominator)));
|
||||
values.set (WavAudioFormat::acidNumerator, String (ByteOrder::swapIfBigEndian (meterNumerator)));
|
||||
values.set (WavAudioFormat::acidTempo, String (swapFloatByteOrder (tempo)));
|
||||
}
|
||||
|
||||
void setBoolFlag (StringPairArray& values, const char* name, int32 mask) const
|
||||
void setBoolFlag (StringPairArray& values, const char* name, uint32 mask) const
|
||||
{
|
||||
values.set (name, (flags & mask) ? "1" : "0");
|
||||
values.set (name, (flags & ByteOrder::swapIfBigEndian (mask)) ? "1" : "0");
|
||||
}
|
||||
|
||||
int32 flags;
|
||||
int16 rootNote;
|
||||
int16 reserved1;
|
||||
static uint32 getFlagIfPresent (const StringPairArray& values, const char* name, uint32 flag)
|
||||
{
|
||||
return values[name].getIntValue() != 0 ? ByteOrder::swapIfBigEndian (flag) : 0;
|
||||
}
|
||||
|
||||
static float swapFloatByteOrder (const float x) noexcept
|
||||
{
|
||||
#ifdef JUCE_BIG_ENDIAN
|
||||
union { uint32 asInt; float asFloat; } n;
|
||||
n.asFloat = x;
|
||||
n.asInt = ByteOrder::swap (n.asInt);
|
||||
return n.asFloat;
|
||||
#else
|
||||
return x;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32 flags;
|
||||
uint16 rootNote;
|
||||
uint16 reserved1;
|
||||
float reserved2;
|
||||
int32 numBeats;
|
||||
int16 meterDenominator;
|
||||
int16 meterNumerator;
|
||||
uint32 numBeats;
|
||||
uint16 meterDenominator;
|
||||
uint16 meterNumerator;
|
||||
float tempo;
|
||||
|
||||
} JUCE_PACKED;
|
||||
|
||||
//==============================================================================
|
||||
struct TracktionChunk
|
||||
{
|
||||
static MemoryBlock createFrom (const StringPairArray& values)
|
||||
{
|
||||
const String s = values[WavAudioFormat::tracktionLoopInfo];
|
||||
MemoryBlock data;
|
||||
|
||||
if (s.isNotEmpty())
|
||||
{
|
||||
MemoryOutputStream os (data, false);
|
||||
os.writeString (s);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
namespace AXMLChunk
|
||||
{
|
||||
@@ -816,6 +924,12 @@ public:
|
||||
{
|
||||
AcidChunk (*input, length).addToMetadata (metadataValues);
|
||||
}
|
||||
else if (chunkType == chunkName ("Trkn"))
|
||||
{
|
||||
MemoryBlock tracktion;
|
||||
input->readIntoMemoryBlock (tracktion, (ssize_t) length);
|
||||
metadataValues.set (WavAudioFormat::tracktionLoopInfo, tracktion.toString());
|
||||
}
|
||||
else if (chunkEnd <= input->getPosition())
|
||||
{
|
||||
break;
|
||||
@@ -913,12 +1027,15 @@ public:
|
||||
// key should be removed (or set to "WAV") once this has been done
|
||||
jassert (metadataValues.getValue ("MetaDataSource", "None") != "AIFF");
|
||||
|
||||
bwavChunk = BWAVChunk::createFrom (metadataValues);
|
||||
axmlChunk = AXMLChunk::createFrom (metadataValues);
|
||||
smplChunk = SMPLChunk::createFrom (metadataValues);
|
||||
instChunk = InstChunk::createFrom (metadataValues);
|
||||
cueChunk = CueChunk ::createFrom (metadataValues);
|
||||
listChunk = ListChunk::createFrom (metadataValues);
|
||||
bwavChunk = BWAVChunk::createFrom (metadataValues);
|
||||
axmlChunk = AXMLChunk::createFrom (metadataValues);
|
||||
smplChunk = SMPLChunk::createFrom (metadataValues);
|
||||
instChunk = InstChunk::createFrom (metadataValues);
|
||||
cueChunk = CueChunk ::createFrom (metadataValues);
|
||||
listChunk = ListChunk::createFrom (metadataValues);
|
||||
listInfoChunk = ListInfoChunk::createFrom (metadataValues);
|
||||
acidChunk = AcidChunk::createFrom (metadataValues);
|
||||
trckChunk = TracktionChunk::createFrom (metadataValues);
|
||||
}
|
||||
|
||||
headerPosition = out->getPosition();
|
||||
@@ -927,12 +1044,6 @@ public:
|
||||
|
||||
~WavAudioFormatWriter()
|
||||
{
|
||||
if ((bytesWritten & 1) != 0) // pad to an even length
|
||||
{
|
||||
++bytesWritten;
|
||||
output->writeByte (0);
|
||||
}
|
||||
|
||||
writeHeader();
|
||||
}
|
||||
|
||||
@@ -972,8 +1083,22 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool flush() override
|
||||
{
|
||||
const int64 lastWritePos = output->getPosition();
|
||||
writeHeader();
|
||||
|
||||
if (output->setPosition (lastWritePos))
|
||||
return true;
|
||||
|
||||
// if this fails, you've given it an output stream that can't seek! It needs
|
||||
// to be able to seek back to write the header
|
||||
jassertfalse;
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
MemoryBlock tempBlock, bwavChunk, axmlChunk, smplChunk, instChunk, cueChunk, listChunk;
|
||||
MemoryBlock tempBlock, bwavChunk, axmlChunk, smplChunk, instChunk, cueChunk, listChunk, listInfoChunk, acidChunk, trckChunk;
|
||||
uint64 lengthInSamples, bytesWritten;
|
||||
int64 headerPosition;
|
||||
bool writeFailed;
|
||||
@@ -998,13 +1123,18 @@ private:
|
||||
|
||||
void writeHeader()
|
||||
{
|
||||
using namespace WavFileHelpers;
|
||||
const bool seekedOk = output->setPosition (headerPosition);
|
||||
(void) seekedOk;
|
||||
if ((bytesWritten & 1) != 0) // pad to an even length
|
||||
output->writeByte (0);
|
||||
|
||||
// if this fails, you've given it an output stream that can't seek! It needs
|
||||
// to be able to seek back to write the header
|
||||
jassert (seekedOk);
|
||||
using namespace WavFileHelpers;
|
||||
|
||||
if (headerPosition != output->getPosition() && ! output->setPosition (headerPosition))
|
||||
{
|
||||
// if this fails, you've given it an output stream that can't seek! It needs to be
|
||||
// able to seek back to go back and write the header after the data has been written.
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t bytesPerFrame = numChannels * bitsPerSample / 8;
|
||||
uint64 audioDataSize = bytesPerFrame * lengthInSamples;
|
||||
@@ -1020,6 +1150,9 @@ private:
|
||||
+ chunkSize (instChunk)
|
||||
+ chunkSize (cueChunk)
|
||||
+ chunkSize (listChunk)
|
||||
+ chunkSize (listInfoChunk)
|
||||
+ chunkSize (acidChunk)
|
||||
+ chunkSize (trckChunk)
|
||||
+ (8 + 28)); // (ds64 chunk)
|
||||
|
||||
riffChunkSize += (riffChunkSize & 1);
|
||||
@@ -1093,12 +1226,15 @@ private:
|
||||
output->write (subFormat.data4, sizeof (subFormat.data4));
|
||||
}
|
||||
|
||||
writeChunk (bwavChunk, chunkName ("bext"));
|
||||
writeChunk (axmlChunk, chunkName ("axml"));
|
||||
writeChunk (smplChunk, chunkName ("smpl"));
|
||||
writeChunk (instChunk, chunkName ("inst"), 7);
|
||||
writeChunk (cueChunk, chunkName ("cue "));
|
||||
writeChunk (listChunk, chunkName ("LIST"));
|
||||
writeChunk (bwavChunk, chunkName ("bext"));
|
||||
writeChunk (axmlChunk, chunkName ("axml"));
|
||||
writeChunk (smplChunk, chunkName ("smpl"));
|
||||
writeChunk (instChunk, chunkName ("inst"), 7);
|
||||
writeChunk (cueChunk, chunkName ("cue "));
|
||||
writeChunk (listChunk, chunkName ("LIST"));
|
||||
writeChunk (listInfoChunk, chunkName ("LIST"));
|
||||
writeChunk (acidChunk, chunkName ("acid"));
|
||||
writeChunk (trckChunk, chunkName ("Trkn"));
|
||||
|
||||
writeChunkHeader (chunkName ("data"), isRF64 ? -1 : (int) (lengthInSamples * bytesPerFrame));
|
||||
|
||||
|
||||
@@ -135,6 +135,9 @@ public:
|
||||
/** Metadata property name used when reading an ISRC code from an AXML chunk. */
|
||||
static const char* const ISRC;
|
||||
|
||||
/** Metadata property name used when reading a WAV file with a Tracktion chunk. */
|
||||
static const char* const tracktionLoopInfo;
|
||||
|
||||
//==============================================================================
|
||||
Array<int> getPossibleSampleRates() override;
|
||||
Array<int> getPossibleBitDepths() override;
|
||||
|
||||
@@ -162,7 +162,7 @@ void AudioFormatReader::read (AudioSampleBuffer* buffer,
|
||||
}
|
||||
else
|
||||
{
|
||||
HeapBlock<int*> chans (numTargetChannels);
|
||||
HeapBlock<int*> chans ((size_t) numTargetChannels);
|
||||
readChannels (*this, chans, buffer, startSample, numSamples, readerStartSample, numTargetChannels);
|
||||
}
|
||||
|
||||
@@ -173,35 +173,54 @@ void AudioFormatReader::read (AudioSampleBuffer* buffer,
|
||||
}
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
static Range<SampleType> getChannelMinAndMax (SampleType* channel, int numSamples) noexcept
|
||||
void AudioFormatReader::readMaxLevels (int64 startSampleInFile, int64 numSamples,
|
||||
Range<float>* const results, const int channelsToRead)
|
||||
{
|
||||
return Range<SampleType>::findMinAndMax (channel, numSamples);
|
||||
}
|
||||
jassert (channelsToRead > 0 && channelsToRead <= (int) numChannels);
|
||||
|
||||
static Range<float> getChannelMinAndMax (float* channel, int numSamples) noexcept
|
||||
{
|
||||
return FloatVectorOperations::findMinAndMax (channel, numSamples);
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
static void getStereoMinAndMax (SampleType* const* channels, const int numChannels, const int numSamples,
|
||||
SampleType& lmin, SampleType& lmax, SampleType& rmin, SampleType& rmax)
|
||||
{
|
||||
Range<SampleType> range (getChannelMinAndMax (channels[0], numSamples));
|
||||
lmax = jmax (lmax, range.getEnd());
|
||||
lmin = jmin (lmin, range.getStart());
|
||||
|
||||
if (numChannels > 1)
|
||||
if (numSamples <= 0)
|
||||
{
|
||||
range = getChannelMinAndMax (channels[1], numSamples);
|
||||
rmax = jmax (rmax, range.getEnd());
|
||||
rmin = jmin (rmin, range.getStart());
|
||||
for (int i = 0; i < channelsToRead; ++i)
|
||||
results[i] = Range<float>();
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
const int bufferSize = (int) jmin (numSamples, (int64) 4096);
|
||||
AudioSampleBuffer tempSampleBuffer ((int) channelsToRead, bufferSize);
|
||||
|
||||
float* const* const floatBuffer = tempSampleBuffer.getArrayOfWritePointers();
|
||||
int* const* intBuffer = reinterpret_cast<int* const*> (floatBuffer);
|
||||
bool isFirstBlock = true;
|
||||
|
||||
while (numSamples > 0)
|
||||
{
|
||||
rmax = lmax;
|
||||
rmin = lmin;
|
||||
const int numToDo = (int) jmin (numSamples, (int64) bufferSize);
|
||||
if (! read (intBuffer, channelsToRead, startSampleInFile, numToDo, false))
|
||||
break;
|
||||
|
||||
for (int i = 0; i < channelsToRead; ++i)
|
||||
{
|
||||
Range<float> r;
|
||||
|
||||
if (usesFloatingPointData)
|
||||
{
|
||||
r = FloatVectorOperations::findMinAndMax (floatBuffer[i], numToDo);
|
||||
}
|
||||
else
|
||||
{
|
||||
Range<int> intRange (Range<int>::findMinAndMax (intBuffer[i], numToDo));
|
||||
|
||||
r = Range<float> (intRange.getStart() / (float) std::numeric_limits<int>::max(),
|
||||
intRange.getEnd() / (float) std::numeric_limits<int>::max());
|
||||
}
|
||||
|
||||
results[i] = isFirstBlock ? r : results[i].getUnionWith (r);
|
||||
}
|
||||
|
||||
isFirstBlock = false;
|
||||
numSamples -= numToDo;
|
||||
startSampleInFile += numToDo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,66 +228,20 @@ void AudioFormatReader::readMaxLevels (int64 startSampleInFile, int64 numSamples
|
||||
float& lowestLeft, float& highestLeft,
|
||||
float& lowestRight, float& highestRight)
|
||||
{
|
||||
if (numSamples <= 0)
|
||||
Range<float> levels[2];
|
||||
readMaxLevels (startSampleInFile, numSamples, levels, jmin (2, (int) numChannels));
|
||||
lowestLeft = levels[0].getStart();
|
||||
highestLeft = levels[0].getEnd();
|
||||
|
||||
if (numChannels > 1)
|
||||
{
|
||||
lowestLeft = 0;
|
||||
lowestRight = 0;
|
||||
highestLeft = 0;
|
||||
highestRight = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const int bufferSize = (int) jmin (numSamples, (int64) 4096);
|
||||
AudioSampleBuffer tempSampleBuffer ((int) numChannels, bufferSize);
|
||||
|
||||
float* const* const floatBuffer = tempSampleBuffer.getArrayOfWritePointers();
|
||||
int* const* intBuffer = reinterpret_cast<int* const*> (floatBuffer);
|
||||
|
||||
if (usesFloatingPointData)
|
||||
{
|
||||
float lmin = 1.0e6f;
|
||||
float lmax = -lmin;
|
||||
float rmin = lmin;
|
||||
float rmax = lmax;
|
||||
|
||||
while (numSamples > 0)
|
||||
{
|
||||
const int numToDo = (int) jmin (numSamples, (int64) bufferSize);
|
||||
if (! read (intBuffer, 2, startSampleInFile, numToDo, false))
|
||||
break;
|
||||
|
||||
numSamples -= numToDo;
|
||||
startSampleInFile += numToDo;
|
||||
getStereoMinAndMax (floatBuffer, (int) numChannels, numToDo, lmin, lmax, rmin, rmax);
|
||||
}
|
||||
|
||||
lowestLeft = lmin;
|
||||
highestLeft = lmax;
|
||||
lowestRight = rmin;
|
||||
highestRight = rmax;
|
||||
lowestRight = levels[1].getStart();
|
||||
highestRight = levels[1].getEnd();
|
||||
}
|
||||
else
|
||||
{
|
||||
int lmax = std::numeric_limits<int>::min();
|
||||
int lmin = std::numeric_limits<int>::max();
|
||||
int rmax = std::numeric_limits<int>::min();
|
||||
int rmin = std::numeric_limits<int>::max();
|
||||
|
||||
while (numSamples > 0)
|
||||
{
|
||||
const int numToDo = (int) jmin (numSamples, (int64) bufferSize);
|
||||
if (! read (intBuffer, 2, startSampleInFile, numToDo, false))
|
||||
break;
|
||||
|
||||
numSamples -= numToDo;
|
||||
startSampleInFile += numToDo;
|
||||
getStereoMinAndMax (intBuffer, (int) numChannels, numToDo, lmin, lmax, rmin, rmax);
|
||||
}
|
||||
|
||||
lowestLeft = lmin / (float) std::numeric_limits<int>::max();
|
||||
highestLeft = lmax / (float) std::numeric_limits<int>::max();
|
||||
lowestRight = rmin / (float) std::numeric_limits<int>::max();
|
||||
highestRight = rmax / (float) std::numeric_limits<int>::max();
|
||||
lowestRight = lowestLeft;
|
||||
highestRight = highestLeft;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -121,6 +121,25 @@ public:
|
||||
bool useReaderLeftChan,
|
||||
bool useReaderRightChan);
|
||||
|
||||
/** Finds the highest and lowest sample levels from a section of the audio stream.
|
||||
|
||||
This will read a block of samples from the stream, and measure the
|
||||
highest and lowest sample levels from the channels in that section, returning
|
||||
these as normalised floating-point levels.
|
||||
|
||||
@param startSample the offset into the audio stream to start reading from. It's
|
||||
ok for this to be beyond the start or end of the stream.
|
||||
@param numSamples how many samples to read
|
||||
@param results this array will be filled with Range values for each channel.
|
||||
The array must contain numChannels elements.
|
||||
@param numChannelsToRead the number of channels of data to scan. This must be
|
||||
more than zero, but not more than the total number of channels
|
||||
that the reader contains
|
||||
@see read
|
||||
*/
|
||||
virtual void readMaxLevels (int64 startSample, int64 numSamples,
|
||||
Range<float>* results, int numChannelsToRead);
|
||||
|
||||
/** Finds the highest and lowest sample levels from a section of the audio stream.
|
||||
|
||||
This will read a block of samples from the stream, and measure the
|
||||
@@ -138,12 +157,9 @@ public:
|
||||
channel (if there is one)
|
||||
@see read
|
||||
*/
|
||||
virtual void readMaxLevels (int64 startSample,
|
||||
int64 numSamples,
|
||||
float& lowestLeft,
|
||||
float& highestLeft,
|
||||
float& lowestRight,
|
||||
float& highestRight);
|
||||
virtual void readMaxLevels (int64 startSample, int64 numSamples,
|
||||
float& lowestLeft, float& highestLeft,
|
||||
float& lowestRight, float& highestRight);
|
||||
|
||||
/** Scans the source looking for a sample whose magnitude is in a specified range.
|
||||
|
||||
|
||||
@@ -183,6 +183,11 @@ bool AudioFormatWriter::writeFromAudioSampleBuffer (const AudioSampleBuffer& sou
|
||||
return writeFromFloatArrays (chans, numSourceChannels, numSamples);
|
||||
}
|
||||
|
||||
bool AudioFormatWriter::flush()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class AudioFormatWriter::ThreadedWriter::Buffer : private TimeSliceClient
|
||||
{
|
||||
@@ -194,6 +199,8 @@ public:
|
||||
writer (w),
|
||||
receiver (nullptr),
|
||||
samplesWritten (0),
|
||||
samplesPerFlush (0),
|
||||
flushSampleCounter (0),
|
||||
isRunning (true)
|
||||
{
|
||||
timeSliceThread.addTimeSliceClient (this);
|
||||
@@ -266,6 +273,18 @@ public:
|
||||
}
|
||||
|
||||
fifo.finishedRead (size1 + size2);
|
||||
|
||||
if (samplesPerFlush > 0)
|
||||
{
|
||||
flushSampleCounter -= size1 + size2;
|
||||
|
||||
if (flushSampleCounter <= 0)
|
||||
{
|
||||
flushSampleCounter = samplesPerFlush;
|
||||
writer->flush();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -279,6 +298,11 @@ public:
|
||||
samplesWritten = 0;
|
||||
}
|
||||
|
||||
void setFlushInterval (int numSamples) noexcept
|
||||
{
|
||||
samplesPerFlush = numSamples;
|
||||
}
|
||||
|
||||
private:
|
||||
AbstractFifo fifo;
|
||||
AudioSampleBuffer buffer;
|
||||
@@ -287,6 +311,7 @@ private:
|
||||
CriticalSection thumbnailLock;
|
||||
IncomingDataReceiver* receiver;
|
||||
int64 samplesWritten;
|
||||
int samplesPerFlush, flushSampleCounter;
|
||||
volatile bool isRunning;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (Buffer)
|
||||
@@ -310,3 +335,8 @@ void AudioFormatWriter::ThreadedWriter::setDataReceiver (AudioFormatWriter::Thre
|
||||
{
|
||||
buffer->setDataReceiver (receiver);
|
||||
}
|
||||
|
||||
void AudioFormatWriter::ThreadedWriter::setFlushInterval (int numSamplesPerFlush) noexcept
|
||||
{
|
||||
buffer->setFlushInterval (numSamplesPerFlush);
|
||||
}
|
||||
|
||||
@@ -91,8 +91,18 @@ public:
|
||||
to pass it into the method.
|
||||
@param numSamples the number of samples to write
|
||||
*/
|
||||
virtual bool write (const int** samplesToWrite,
|
||||
int numSamples) = 0;
|
||||
virtual bool write (const int** samplesToWrite, int numSamples) = 0;
|
||||
|
||||
/** Some formats may support a flush operation that makes sure the file is in a
|
||||
valid state before carrying on.
|
||||
If supported, this means that by calling flush periodically when writing data
|
||||
to a large file, then it should still be left in a readable state if your program
|
||||
crashes.
|
||||
It goes without saying that this method must be called from the same thread that's
|
||||
calling write()!
|
||||
If the format supports flushing and the operation succeeds, this returns true.
|
||||
*/
|
||||
virtual bool flush();
|
||||
|
||||
//==============================================================================
|
||||
/** Reads a section of samples from an AudioFormatReader, and writes these to
|
||||
@@ -197,7 +207,12 @@ public:
|
||||
|
||||
The object passed-in must not be deleted while this writer is still using it.
|
||||
*/
|
||||
void setDataReceiver (IncomingDataReceiver* receiver);
|
||||
void setDataReceiver (IncomingDataReceiver*);
|
||||
|
||||
/** Sets how many samples should be written before calling the AudioFormatWriter::flush method.
|
||||
Set this to 0 to disable flushing (this is the default).
|
||||
*/
|
||||
void setFlushInterval (int numSamplesPerFlush) noexcept;
|
||||
|
||||
private:
|
||||
class Buffer;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "juce_audio_formats",
|
||||
"name": "JUCE audio file format codecs",
|
||||
"version": "3.0.5",
|
||||
"version": "3.0.8",
|
||||
"description": "Classes for reading and writing various audio file formats.",
|
||||
"website": "http://www.juce.com/juce",
|
||||
"license": "GPL/Commercial",
|
||||
|
||||
@@ -59,12 +59,12 @@ SamplerSound::~SamplerSound()
|
||||
{
|
||||
}
|
||||
|
||||
bool SamplerSound::appliesToNote (const int midiNoteNumber)
|
||||
bool SamplerSound::appliesToNote (int midiNoteNumber)
|
||||
{
|
||||
return midiNotes [midiNoteNumber];
|
||||
}
|
||||
|
||||
bool SamplerSound::appliesToChannel (const int /*midiChannel*/)
|
||||
bool SamplerSound::appliesToChannel (int /*midiChannel*/)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -127,7 +127,7 @@ void SamplerVoice::startNote (const int midiNoteNumber,
|
||||
}
|
||||
}
|
||||
|
||||
void SamplerVoice::stopNote (const bool allowTailOff)
|
||||
void SamplerVoice::stopNote (float /*velocity*/, bool allowTailOff)
|
||||
{
|
||||
if (allowTailOff)
|
||||
{
|
||||
@@ -197,7 +197,7 @@ void SamplerVoice::renderNextBlock (AudioSampleBuffer& outputBuffer, int startSa
|
||||
|
||||
if (attackReleaseLevel <= 0.0f)
|
||||
{
|
||||
stopNote (false);
|
||||
stopNote (0.0f, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -216,7 +216,7 @@ void SamplerVoice::renderNextBlock (AudioSampleBuffer& outputBuffer, int startSa
|
||||
|
||||
if (sourceSamplePosition > playingSound->length)
|
||||
{
|
||||
stopNote (false);
|
||||
stopNote (0.0f, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,8 +82,8 @@ public:
|
||||
|
||||
|
||||
//==============================================================================
|
||||
bool appliesToNote (const int midiNoteNumber) override;
|
||||
bool appliesToChannel (const int midiChannel) override;
|
||||
bool appliesToNote (int midiNoteNumber) override;
|
||||
bool appliesToChannel (int midiChannel) override;
|
||||
|
||||
|
||||
private:
|
||||
@@ -124,7 +124,7 @@ public:
|
||||
bool canPlaySound (SynthesiserSound*) override;
|
||||
|
||||
void startNote (int midiNoteNumber, float velocity, SynthesiserSound*, int pitchWheel) override;
|
||||
void stopNote (bool allowTailOff) override;
|
||||
void stopNote (float velocity, bool allowTailOff) override;
|
||||
|
||||
void pitchWheelMoved (int newValue);
|
||||
void controllerMoved (int controllerNumber, int newValue) override;
|
||||
|
||||
Reference in New Issue
Block a user