Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WavPack decoding support #168

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
315 changes: 315 additions & 0 deletions modules/juce_audio_formats/codecs/juce_WavPackAudioFormat.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
/*
==============================================================================

This file is part of the JUCE library.
Copyright (c) 2015 - ROLI Ltd.

Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3

Details of these licenses can be found at: www.gnu.org/licenses

JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.

------------------------------------------------------------------------------

To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.

==============================================================================
*/

#if JUCE_USE_WAVPACK

namespace WavPackNamespace
{
#include <wavpack/wavpack.h>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in true JUCE fashion wavpack's files would be added locally to the module.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we don't generally include 3rd party headers unless really necessary. What is in wavpack.h and where would someone get it? Does it rely on a static library to go with it, or is it header only?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like you can directly get the source: http://www.wavpack.com/downloads.html#sources

Alternatively, you could add a git submodule for its (official?) repository: https://github.com/dbry/WavPack

}

//==============================================================================
static const char* const wavPackFormatName = "WavPack file";

//==============================================================================
class WavPackReader : public AudioFormatReader
{
public:
WavPackReader (InputStream* const inp)
: AudioFormatReader (inp, wavPackFormatName),
reservoirStart (0),
samplesInReservoir (0),
sampleBuffer (nullptr)
{
using namespace WavPackNamespace;
sampleRate = 0;
usesFloatingPointData = true;

wvReader.read_bytes = &wv_read_bytes;
wvReader.get_pos = &wv_get_pos;
wvReader.set_pos_abs = &wv_set_pos_abs;
wvReader.set_pos_rel = &wv_set_pos_rel;
wvReader.get_length = &wv_get_length;
wvReader.can_seek = &wv_can_seek;
wvReader.push_back_byte = &wv_push_back_byte;

wvContext = WavpackOpenFileInputEx (&wvReader, input, nullptr, wvErrorBuffer, OPEN_NORMALIZE, 0);

if (wvContext != nullptr)
{
lengthInSamples = (uint32) WavpackGetNumSamples(wvContext);
numChannels = (unsigned int) WavpackGetNumChannels(wvContext);
bitsPerSample = WavpackGetBitsPerSample(wvContext);
sampleRate = WavpackGetSampleRate(wvContext);

reservoir.setSize ((int) numChannels, (int) jmin (lengthInSamples, (int64) 4096));
}
}

~WavPackReader()
{
using namespace WavPackNamespace;
WavpackCloseFile (wvContext);
}

//==============================================================================
bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer,
int64 startSampleInFile, int numSamples) override
{
using namespace WavPackNamespace;
while (numSamples > 0)
{
const int numAvailable = (int) (reservoirStart + samplesInReservoir - startSampleInFile);

if (startSampleInFile >= reservoirStart && numAvailable > 0)
{
// got a few samples overlapping, so use them before seeking..
const int numToUse = jmin (numSamples, numAvailable);

for (int i = jmin (numDestChannels, reservoir.getNumChannels()); --i >= 0;)
if (destSamples[i] != nullptr)
memcpy (destSamples[i] + startOffsetInDestBuffer,
reservoir.getReadPointer (i, (int) (startSampleInFile - reservoirStart)),
sizeof (float) * (size_t) numToUse);

startSampleInFile += numToUse;
numSamples -= numToUse;
startOffsetInDestBuffer += numToUse;

if (numSamples == 0)
break;
}

if (startSampleInFile < reservoirStart
|| startSampleInFile + numSamples > reservoirStart + samplesInReservoir)
{
// buffer miss, so refill the reservoir
reservoirStart = jmax (0, (int) startSampleInFile);
samplesInReservoir = reservoir.getNumSamples();

if (reservoirStart != (int) WavpackGetSampleIndex (wvContext))
WavpackSeekSample (wvContext, reservoirStart);

int offset = 0;
int numToRead = samplesInReservoir;

while (numToRead > 0)
{
// initialize buffer
if (sampleBuffer == nullptr)
{
sampleBufferSize = numToRead * numChannels;
sampleBuffer = new int32_t[numToRead * numChannels];
}

// reallocate if buffer size is too small
if (sampleBufferSize < numToRead * numChannels)
{
sampleBufferSize = numToRead * numChannels;
delete []sampleBuffer;
sampleBuffer = new int32_t[sampleBufferSize];
}

const long samps = WavpackUnpackSamples (wvContext, sampleBuffer, numToRead);

if (samps <= 0)
break;

jassert (samps <= numToRead);

auto p1 = reservoir.getWritePointer (0, offset);
auto p2 = reservoir.getWritePointer (1, offset);

float *fp1 = p1;
float *fp2 = p2;
int32_t *in = sampleBuffer;

float maxF = 1.0f;
if (bitsPerSample == 16)
maxF = 32767.0f;
else if (bitsPerSample == 24)
maxF = 8388607.0f;
else if (bitsPerSample == 32)
maxF = 32768.0f * 65536.0f;

if (WavpackGetMode(wvContext) & MODE_FLOAT)
maxF = 1.0f;

for (int i = 0; i < samps; ++i)
{
*fp1 = (float)*in / maxF;
in++;
fp1++;

*fp2 = (float)*in / maxF;
in++;
fp2++;
}

numToRead -= samps;
offset += samps;
}

if (numToRead > 0)
reservoir.clear (offset, numToRead);
}
}

if (numSamples > 0)
{
for (int i = numDestChannels; --i >= 0;)
if (destSamples[i] != nullptr)
zeromem (destSamples[i] + startOffsetInDestBuffer, sizeof (int) * (size_t) numSamples);
}

return true;
}

//==============================================================================
static int32_t wv_read_bytes (void *id, void *data, int32_t bcount)
{
return (int32_t) (static_cast<InputStream*> (id)->read (data, (int) bcount));
}

static uint32_t wv_get_pos (void *id)
{
InputStream* const in = static_cast<InputStream*> (id);
return in->getPosition ();
}

static int wv_set_pos_abs (void *id, uint32_t pos)
{
InputStream* const in = static_cast<InputStream*> (id);
in->setPosition (pos);
return 0;
}

static int wv_push_back_byte (void *id, int c)
{
InputStream* const in = static_cast<InputStream*> (id);

if (0 == in->setPosition (in->getPosition() - 1))
{
return EOF;
}

return c;
}

static int wv_set_pos_rel (void *id, int32_t delta, int mode)
{
InputStream* const in = static_cast<InputStream*> (id);

if (mode == SEEK_CUR)
delta += in->getPosition();
else if (mode == SEEK_END)
delta += in->getTotalLength();

in->setPosition (delta);
return 0;
}

static uint32_t wv_get_length (void *id)
{
return static_cast<InputStream*> (id)->getTotalLength ();
}

static int wv_can_seek (void *id)
{
return 1;
}

private:
WavPackNamespace::WavpackStreamReader wvReader;
WavPackNamespace::WavpackContext* wvContext;
char wvErrorBuffer[80];
AudioSampleBuffer reservoir;
int reservoirStart, samplesInReservoir;
int32_t *sampleBuffer;
size_t sampleBufferSize;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavPackReader)
};

//==============================================================================
WavPackAudioFormat::WavPackAudioFormat() : AudioFormat (wavPackFormatName, ".wv")
{
}

WavPackAudioFormat::~WavPackAudioFormat()
{
}

Array<int> WavPackAudioFormat::getPossibleSampleRates()
{
const int rates[] = { 8000, 11025, 12000, 16000, 22050, 32000,
44100, 48000, 88200, 96000, 176400, 192000 };

return Array<int> (rates, numElementsInArray (rates));
}

Array<int> WavPackAudioFormat::getPossibleBitDepths()
{
const int depths[] = { 16, 24, 32 };

return Array<int> (depths, numElementsInArray (depths));
}

bool WavPackAudioFormat::canDoStereo() { return true; }
bool WavPackAudioFormat::canDoMono() { return true; }
bool WavPackAudioFormat::isCompressed() { return true; }

AudioFormatReader* WavPackAudioFormat::createReaderFor (InputStream* in, const bool deleteStreamIfOpeningFails)
{
ScopedPointer<WavPackReader> r (new WavPackReader (in));

if (r->sampleRate > 0)
{
return r.release();
}

if (! deleteStreamIfOpeningFails)
r->input = nullptr;

return nullptr;
}

AudioFormatWriter* WavPackAudioFormat::createWriterFor (OutputStream* out,
double sampleRate,
unsigned int numChannels,
int bitsPerSample,
const StringPairArray& metadataValues,
int qualityOptionIndex)
{
jassertfalse; // not yet implemented!
return nullptr;
}

StringArray WavPackAudioFormat::getQualityOptions()
{
static const char* options[] = { "fast", "high", "very high", 0 };
return StringArray (options);
}

#endif
66 changes: 66 additions & 0 deletions modules/juce_audio_formats/codecs/juce_WavPackAudioFormat.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
==============================================================================

This file is part of the JUCE library.
Copyright (c) 2015 - ROLI Ltd.

Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3

Details of these licenses can be found at: www.gnu.org/licenses

JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.

------------------------------------------------------------------------------

To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.

==============================================================================
*/

#if JUCE_USE_WAVPACK || defined (DOXYGEN)

//==============================================================================
/**
Reads the WavPack audio format.

To compile this, you'll need to set the JUCE_USE_WAVPACK flag.

@see AudioFormat,
*/
class JUCE_API WavPackAudioFormat : public AudioFormat
{
public:
//==============================================================================
WavPackAudioFormat();
~WavPackAudioFormat();

//==============================================================================
Array<int> getPossibleSampleRates() override;
Array<int> getPossibleBitDepths() override;
bool canDoStereo() override;
bool canDoMono() override;
bool isCompressed() override;
StringArray getQualityOptions() override;

//==============================================================================
AudioFormatReader* createReaderFor (InputStream* sourceStream,
bool deleteStreamIfOpeningFails) override;

AudioFormatWriter* createWriterFor (OutputStream* streamToWriteTo,
double sampleRateToUse,
unsigned int numberOfChannels,
int bitsPerSample,
const StringPairArray& metadataValues,
int qualityOptionIndex) override;

private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavPackAudioFormat)
};


#endif
4 changes: 4 additions & 0 deletions modules/juce_audio_formats/format/juce_AudioFormatManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ void AudioFormatManager::registerBasicFormats()
#if JUCE_USE_WINDOWS_MEDIA_FORMAT
registerFormat (new WindowsMediaAudioFormat(), false);
#endif

#if JUCE_USE_WAVPACK
registerFormat (new WavPackAudioFormat(), false);
#endif
}

void AudioFormatManager::clearFormats()
Expand Down
Loading