diff --git a/src/AudioDecoder.cpp b/src/AudioDecoder.cpp new file mode 100644 index 0000000..0fb1ae7 --- /dev/null +++ b/src/AudioDecoder.cpp @@ -0,0 +1,88 @@ +#pragma comment(user, "license") + +#include "AudioDecoder.h" + +#include "WavDecoder.h" +#include "WavPackDecoder.h" +#include "FlacDecoder.h" +#include "VorbisDecoder.h" +#include "OpusDecoder.h" +#include "CafDecoder.h" + +using namespace nqr; + +NyquistIO::NyquistIO() +{ + BuildDecoderTable(); +} + +NyquistIO::~NyquistIO() +{ + +} + +int NyquistIO::Load(AudioData * data, const std::string & path) +{ + if (IsFileSupported(path)) + { + if (decoderTable.size() > 0) + { + auto fileExtension = ParsePathForExtension(path); + auto decoder = GetDecoderForExtension(fileExtension); + + try + { + return decoder->LoadFromPath(data, path); + } + catch (std::exception e) + { + std::cerr << "Caught fatal exception: " << e.what() << std::endl; + } + + } + return IOError::NoDecodersLoaded; + } + else + { + return IOError::ExtensionNotSupported; + } + + // Should never be reached + return IOError::UnknownError; +} + +bool NyquistIO::IsFileSupported(const std::string path) const +{ + auto fileExtension = ParsePathForExtension(path); + if (decoderTable.find(fileExtension) == decoderTable.end()) + { + return false; + } + else + { + return true; + } +} + +std::string NyquistIO::ParsePathForExtension(const std::string & path) const +{ + if (path.find_last_of(".") != std::string::npos) + return path.substr(path.find_last_of(".") + 1); + + return std::string(""); +} + +std::shared_ptr NyquistIO::GetDecoderForExtension(const std::string ext) +{ + return decoderTable[ext]; +} + +void NyquistIO::BuildDecoderTable() +{ + AddDecoderToTable(std::make_shared()); + AddDecoderToTable(std::make_shared()); + AddDecoderToTable(std::make_shared()); + AddDecoderToTable(std::make_shared()); + AddDecoderToTable(std::make_shared()); + AddDecoderToTable(std::make_shared()); +} \ No newline at end of file diff --git a/src/AudioDevice.cpp b/src/AudioDevice.cpp new file mode 100644 index 0000000..61872db --- /dev/null +++ b/src/AudioDevice.cpp @@ -0,0 +1,81 @@ +#pragma comment(user, "license") + +#include "AudioDevice.h" +#include +#include +#include +#include + +using namespace nqr; + +static RingBufferT buffer(BUFFER_LENGTH); + +static int rt_callback(void * output_buffer, void * input_buffer, unsigned int num_bufferframes, double stream_time, RtAudioStreamStatus status, void * user_data) +{ + if (status) std::cerr << "[rtaudio] Buffer over or underflow" << std::endl; + + if (buffer.getAvailableRead()) + { + buffer.read((float*) output_buffer, BUFFER_LENGTH); + } + else + { + memset(output_buffer, 0, BUFFER_LENGTH * sizeof(float)); + } + + return 0; +} + +bool AudioDevice::Open(const int deviceId) +{ + if (!rtaudio) throw std::runtime_error("rtaudio not created yet"); + + RtAudio::StreamParameters parameters; + parameters.deviceId = info.id; + parameters.nChannels = info.numChannels; + parameters.firstChannel = 0; + + rtaudio->openStream(¶meters, NULL, RTAUDIO_FLOAT32, info.sampleRate, &info.frameSize, &rt_callback, (void*) & buffer); + + if (rtaudio->isStreamOpen()) + { + rtaudio->startStream(); + return true; + } + return false; +} + +void AudioDevice::ListAudioDevices() +{ + std::unique_ptr tempDevice(new RtAudio); + + RtAudio::DeviceInfo info; + unsigned int devices = tempDevice->getDeviceCount(); + + std::cout << "[rtaudio] Found: " << devices << " device(s)\n"; + + for (unsigned int i = 0; i < devices; ++i) + { + info = tempDevice->getDeviceInfo(i); + std::cout << "\tDevice: " << i << " - " << info.name << std::endl; + } + std::cout << std::endl; +} + +bool AudioDevice::Play(const std::vector & data) +{ + if (!rtaudio->isStreamOpen()) return false; + + // Each frame is the (size/2) cause interleaved channels! + int sizeInFrames = ((int) data.size()) / (BUFFER_LENGTH); + + int writeCount = 0; + + while(writeCount < sizeInFrames) + { + bool status = buffer.write((data.data() + (writeCount * BUFFER_LENGTH)), BUFFER_LENGTH); + if (status) writeCount++; + } + + return true; +} \ No newline at end of file diff --git a/src/CafDecoder.cpp b/src/CafDecoder.cpp new file mode 100644 index 0000000..8993589 --- /dev/null +++ b/src/CafDecoder.cpp @@ -0,0 +1,24 @@ +#pragma comment(user, "license") + +#include "CafDecoder.h" + +using namespace nqr; + +////////////////////// +// Public Interface // +////////////////////// + +int CAFDecoder::LoadFromPath(AudioData * data, const std::string & path) +{ + return IOError::LoadPathNotImplemented; +} + +int CAFDecoder::LoadFromBuffer(AudioData * data, const std::vector & memory) +{ + return IOError::LoadBufferNotImplemented; +} + +std::vector CAFDecoder::GetSupportedFileExtensions() +{ + return {}; +} \ No newline at end of file diff --git a/src/Common.cpp b/src/Common.cpp new file mode 100644 index 0000000..e567a93 --- /dev/null +++ b/src/Common.cpp @@ -0,0 +1,56 @@ +#include "Common.h" + +using namespace nqr; + +ChunkHeaderInfo nqr::ScanForChunk(const std::vector & fileData, uint32_t chunkMarker) +{ + // D[n] aligned to 16 bytes now + const uint16_t * d = reinterpret_cast(fileData.data()); + + for (size_t i = 0; i < fileData.size() / sizeof(uint16_t); i++) + { + // This will be in machine endianess + uint32_t m = Pack(Read16(d[i]), Read16(d[i+1])); + + if (m == chunkMarker) + { + uint32_t cSz = Pack(Read16(d[i+2]), Read16(d[i+3])); + return {(uint32_t (i * sizeof(uint16_t))), cSz}; // return i in bytes to the start of the data + } + else continue; + } + return {0, 0}; +}; + +NyquistFileBuffer nqr::ReadFile(std::string pathToFile) +{ + std::cout << "[Debug] Open: " << pathToFile << std::endl; + + FILE * audioFile = fopen(pathToFile.c_str(), "rb"); + + if (!audioFile) + { + throw std::runtime_error("file not found"); + } + + fseek(audioFile, 0, SEEK_END); + size_t lengthInBytes = ftell(audioFile); + fseek(audioFile, 0, SEEK_SET); + + // Allocate temporary buffer + std::vector fileBuffer(lengthInBytes); + + size_t elementsRead = fread(fileBuffer.data(), 1, lengthInBytes, audioFile); + + if (elementsRead == 0 || fileBuffer.size() < 64) + { + throw std::runtime_error("error reading file or file too small"); + } + + NyquistFileBuffer data = {std::move(fileBuffer), elementsRead}; + + fclose(audioFile); + + // Copy out to user + return data; +} diff --git a/src/FlacDecoder.cpp b/src/FlacDecoder.cpp new file mode 100644 index 0000000..44342a8 --- /dev/null +++ b/src/FlacDecoder.cpp @@ -0,0 +1,177 @@ +#pragma comment(user, "license") + +#include "FlacDecoder.h" +#include "flac/all.h" +#include "AudioDecoder.h" + +using namespace nqr; + +class FlacDecoderInternal +{ + +public: + + // N.B.: FLAC is a big-endian format. All values are unsigned. + FlacDecoderInternal(AudioData * d, std::string filepath) : d(d) + { + + ///////////////////////////// + // Initialize FLAC library // + ///////////////////////////// + + decoderInternal = FLAC__stream_decoder_new(); + + FLAC__stream_decoder_set_metadata_respond(decoderInternal, FLAC__METADATA_TYPE_STREAMINFO); + + //@todo: check if OGG flac + bool initialized = FLAC__stream_decoder_init_file(decoderInternal, + filepath.c_str(), + s_writeCallback, + s_metadataCallback, + s_errorCallback, + this) == FLAC__STREAM_DECODER_INIT_STATUS_OK; + + FLAC__stream_decoder_set_md5_checking(decoderInternal, true); + + ////////////////////// + // Read Stream Data // + ///////////////////// + + if (initialized) + { + // Find the size and allocate memory + FLAC__stream_decoder_process_until_end_of_metadata(decoderInternal); + + // Read memory out into our temporary internalBuffer + FLAC__stream_decoder_process_until_end_of_stream(decoderInternal); + + // Presently unneeded, but useful for reference + // FLAC__ChannelAssignment channelAssignment = FLAC__stream_decoder_get_channel_assignment(decoderInternal); + + // Fill out remaining user data + d->lengthSeconds = (float) numSamples / (float) d->sampleRate; + + auto totalSamples = numSamples * d->channelCount; + + // N.B.: "Currently the reference encoder and decoders only support up to 24 bits per sample." + + PCMFormat internalFmt = PCMFormat::PCM_END; + + switch (d->bitDepth) + { + case 8: + internalFmt = PCMFormat::PCM_S8; + break; + case 16: + internalFmt = PCMFormat::PCM_16; + break; + case 24: + internalFmt = PCMFormat::PCM_24; + break; + default: + throw std::runtime_error("unsupported FLAC bit depth"); + break; + } + + // Next, process internal buffer into the user-visible samples array + ConvertToFloat32(d->samples.data(), internalBuffer.data(), totalSamples, internalFmt); + } + + else + { + throw std::runtime_error("Unable to initialize FLAC decoder"); + } + + } + + ~FlacDecoderInternal() + { + if (decoderInternal) + { + FLAC__stream_decoder_finish(decoderInternal); + FLAC__stream_decoder_delete(decoderInternal); + } + } + + void processMetadata(const FLAC__StreamMetadata_StreamInfo & info) + { + d->sampleRate = info.sample_rate; + d->channelCount = info.channels; // Assert 1 to 8 + d->bitDepth = info.bits_per_sample; // Assert 4 to 32 + d->frameSize = info.channels * info.bits_per_sample; + + const size_t bytesPerSample = d->bitDepth / 8; + + numSamples = (size_t) info.total_samples; + + internalBuffer.resize(numSamples * info.channels * bytesPerSample); // as array of bytes + d->samples.resize(numSamples * info.channels); // as audio samples in float32 + } + + /////////////////////// + // libflab callbacks // + /////////////////////// + + static FLAC__StreamDecoderWriteStatus s_writeCallback(const FLAC__StreamDecoder*, const FLAC__Frame* frame, const FLAC__int32* const buffer[], void* userPtr) + { + FlacDecoderInternal * decoder = reinterpret_cast(userPtr); + + const size_t bytesPerSample = decoder->d->bitDepth / 8; + + auto dataPtr = decoder->internalBuffer.data(); + + for(unsigned int i = 0; i < frame->header.blocksize; i++) + { + for(int j = 0; j < decoder->d->channelCount; j++) + { + memcpy(dataPtr + decoder->bufferPosition, &buffer[j][i], bytesPerSample); + decoder->bufferPosition += bytesPerSample; + } + } + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; + } + + static void s_metadataCallback (const FLAC__StreamDecoder*, const FLAC__StreamMetadata* metadata, void* userPtr) + { + static_cast(userPtr)->processMetadata(metadata->data.stream_info); + } + + static void s_errorCallback (const FLAC__StreamDecoder *, FLAC__StreamDecoderErrorStatus status, void*) + { + std::cerr << "FLAC Decoder Error: " << FLAC__StreamDecoderErrorStatusString[status] << std::endl; + } + +private: + + NO_COPY(FlacDecoderInternal); + + FLAC__StreamDecoder * decoderInternal; + + size_t bufferPosition = 0; + size_t numSamples = 0; + + AudioData * d; + + std::vector internalBuffer; +}; + +////////////////////// +// Public Interface // +////////////////////// + +int FlacDecoder::LoadFromPath(AudioData * data, const std::string & path) +{ + FlacDecoderInternal decoder(data, path); + return IOError::NoError; +} + +int FlacDecoder::LoadFromBuffer(AudioData * data, const std::vector & memory) +{ + return IOError::LoadBufferNotImplemented; +} + +std::vector FlacDecoder::GetSupportedFileExtensions() +{ + return {"flac"}; +} \ No newline at end of file diff --git a/src/FlacDependencies.c b/src/FlacDependencies.c new file mode 100644 index 0000000..7d0846e --- /dev/null +++ b/src/FlacDependencies.c @@ -0,0 +1,84 @@ +#pragma comment(user, "license") + +#undef VERSION +#define VERSION "1.3.1" + +#define FLAC__NO_DLL 1 + +#if (_MSC_VER) +#pragma warning (push) +#pragma warning (disable: 181 111 4267 4996 4244 4701 4702 4133 4100 4127 4206 4312 4505 4365 4005 4013 4334) +#ifndef _WIN32 +#define _WIN32 +#endif +#endif + +#if defined(__APPLE__) && defined(__MACH__) +#define FLAC__SYS_DARWIN 1 +#endif + +#ifndef SIZE_MAX +#define SIZE_MAX (size_t) (-1) +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wconversion" +#pragma clang diagnostic ignored "-Wshadow" +#pragma clang diagnostic ignored "-Wdeprecated-register" +#endif + +#if CPU_X86 +#ifdef __i386__ +#define FLAC__CPU_IA32 1 +#endif +#ifdef __x86_64__ +#define FLAC__CPU_X86_64 1 +#endif +#define FLAC__HAS_X86INTRIN 1 +#endif + +// Ensure libflac can use non-standard types +#undef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 + +#if defined(__APPLE__) && defined(__MACH__) +#define flac_max(a,b) ((a) > (b) ? a : b) +#define flac_min(a,b) ((a) < (b) ? a : b) +#elif defined(_MSC_VER) +#include +#define flac_max(a,b) __max(a,b) +#define flac_min(a,b) __min(a,b) +#endif + +#define HAVE_LROUND 1 + +#include "flac/all.h" + +#if defined(_MSC_VER) +#include "flac/src/win_utf8_io.c" +#endif + +#include "flac/src/bitmath.c" +#include "flac/src/bitreader.c" +#include "flac/src/bitwriter.c" +#include "flac/src/cpu.c" +#include "flac/src/crc.c" +#include "flac/src/fixed.c" +#include "flac/src/float.c" +#include "flac/src/format.c" +#include "flac/src/lpc.c" +#include "flac/src/md5.c" +#include "flac/src/memory.c" +#include "flac/src/stream_decoder.c" +#include "flac/src/window.c" + +#undef VERSION + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#if (_MSC_VER) +#pragma warning (pop) +#endif \ No newline at end of file diff --git a/src/Main.cpp b/src/Main.cpp new file mode 100644 index 0000000..0c8be94 --- /dev/null +++ b/src/Main.cpp @@ -0,0 +1,96 @@ +#if defined(_MSC_VER) +#pragma comment(lib, "dsound.lib") +#endif + +#include "AudioDevice.h" +#include "AudioDecoder.h" + +#include + +using namespace nqr; + +int main() +{ + AudioDevice::ListAudioDevices(); + + int desiredSampleRate = 44100; + AudioDevice myDevice(2, desiredSampleRate); + myDevice.Open(myDevice.info.id); + + AudioData * fileData = new AudioData(); + + NyquistIO loader; + + try + { + + //auto result = loader.Load(fileData, "test_data/1ch/44100/8/test.wav"); + //auto result = loader.Load(fileData, "test_data/1ch/44100/16/test.wav"); + //auto result = loader.Load(fileData, "test_data/1ch/44100/24/test.wav"); + //auto result = loader.Load(fileData, "test_data/1ch/44100/32/test.wav"); + //auto result = loader.Load(fileData, "test_data/1ch/44100/64/test.wav"); + + //auto result = loader.Load(fileData, "test_data/2ch/44100/8/test.wav"); + //auto result = loader.Load(fileData, "test_data/2ch/44100/16/test.wav"); + //auto result = loader.Load(fileData, "test_data/2ch/44100/24/test.wav"); + auto result = loader.Load(fileData, "test_data/2ch/44100/32/test.wav"); + //auto result = loader.Load(fileData, "test_data/2ch/44100/64/test.wav"); + + //auto result = loader.Load(fileData, "test_data/ad_hoc/6_channel_44k_16b.wav"); + + //auto result = loader.Load(fileData, "test_data/ad_hoc/LR_Stereo.ogg"); + //auto result = loader.Load(fileData, "test_data/ad_hoc/TestLaugh_44k.ogg"); + //auto result = loader.Load(fileData, "test_data/ad_hoc/TestBeat.ogg"); + //auto result = loader.Load(fileData, "test_data/ad_hoc/TestBeatMono.ogg"); + //auto result = loader.Load(fileData, "test_data/ad_hoc/BlockWoosh_Stereo.ogg"); + + //auto result = loader.Load(fileData, "test_data/ad_hoc/KittyPurr8_Stereo_Dithered.flac"); + //auto result = loader.Load(fileData, "test_data/ad_hoc/KittyPurr16_Stereo.flac"); + //auto result = loader.Load(fileData, "test_data/ad_hoc/KittyPurr16_Mono.flac"); + //auto result = loader.Load(fileData, "test_data/ad_hoc/KittyPurr24_Stereo.flac"); + + //auto result = loader.Load(fileData, "test_data/ad_hoc/detodos.opus"); // "Firefox: From All, To All" + + //auto result = loader.Load(fileData, "test_data/ad_hoc/TestBeat_Float32.wv"); + //auto result = loader.Load(fileData, "test_data/ad_hoc/TestBeat_Float32_Mono.wv"); + //auto result = loader.Load(fileData, "test_data/ad_hoc/TestBeat_Int16.wv"); + //auto result = loader.Load(fileData, "test_data/ad_hoc/TestBeat_Int24.wv"); + //auto result = loader.Load(fileData, "test_data/ad_hoc/TestBeat_Int32.wv"); + //auto result = loader.Load(fileData, "test_data/ad_hoc/TestBeat_Int24_Mono.wv"); + + std::cout << "[Debug] Loader Status: " << result << std::endl; + } + catch (std::exception e) + { + std::cerr << "Caught: " << e.what() << std::endl; + std::exit(1); + } + + // Libnyquist does not do sample rate conversion + if (fileData->sampleRate != desiredSampleRate) + { + std::cout << "[Warning - Sample Rate Mismatch] - file is sampled at " << fileData->sampleRate << " and output is " << desiredSampleRate << std::endl; + } + + // Convert mono to stereo for testing playback + if (fileData->channelCount == 1) + { + std::vector stereoCopy(fileData->samples.size() * 2); + + int m = 0; + for (size_t i = 0; i < stereoCopy.size(); i+=2) + { + stereoCopy[i] = fileData->samples[m]; + stereoCopy[i+1] = fileData->samples[m]; + m++; + } + myDevice.Play(stereoCopy); + } + else + { + std::cout << "Playing for: " << fileData->lengthSeconds << " seconds..." << std::endl; + myDevice.Play(fileData->samples); + } + + return 0; +} \ No newline at end of file diff --git a/src/OpusDecoder.cpp b/src/OpusDecoder.cpp new file mode 100644 index 0000000..12ac7fd --- /dev/null +++ b/src/OpusDecoder.cpp @@ -0,0 +1,156 @@ +#pragma comment(user, "license") + +#include "OpusDecoder.h" + +using namespace nqr; + +static const int OPUS_SAMPLE_RATE = 48000; + +class OpusDecoderInternal +{ + +public: + + OpusDecoderInternal(AudioData * d, const std::vector & fileData) : d(d) + { + /* @todo proper steaming support + classes + const opus_callbacks = { + .read = s_readCallback, + .seek = s_seekCallback, + .tell = s_tellCallback, + .close = nullptr + }; + */ + + int err; + + fileHandle = op_test_memory(fileData.data(), fileData.size(), &err); + + if (!fileHandle) + { + std::cerr << errorAsString(err) << std::endl; + throw std::runtime_error("File is not a valid ogg vorbis file"); + } + + if (auto r = op_test_open(fileHandle) != 0) + { + std::cerr << errorAsString(r) << std::endl; + throw std::runtime_error("Could not open file"); + } + + const OpusHead *header = op_head(fileHandle, 0); + + int originalSampleRate = header->input_sample_rate; + + std::cout << "Original Sample Rate: " << originalSampleRate << std::endl; + + d->sampleRate = OPUS_SAMPLE_RATE; + d->channelCount = (uint32_t) header->channel_count; + d->bitDepth = 32; + d->lengthSeconds = double(getLengthInSeconds()); + d->frameSize = (uint32_t) header->channel_count * d->bitDepth; + + // Samples in a single channel + auto totalSamples = size_t(getTotalSamples()); + + d->samples.resize(totalSamples * d->channelCount); + + auto r = readInternal(totalSamples); + } + + ~OpusDecoderInternal() + { + op_free(fileHandle); + } + + size_t readInternal(size_t requestedFrameCount, size_t frameOffset = 0) + { + float *buffer = (float *) d->samples.data(); + size_t framesRemaining = requestedFrameCount; + size_t totalFramesRead = 0; + + while(0 < framesRemaining) + { + int64_t framesRead = op_read_float(fileHandle, buffer, (int)(framesRemaining * d->channelCount), nullptr); + + // EOF + if(!framesRead) + break; + + if (framesRead < 0) + { + std::cerr << "Opus decode error: " << framesRead << std::endl; + return 0; + } + + buffer += framesRead * d->channelCount; + + totalFramesRead += framesRead; + framesRemaining -= framesRead; + } + + return totalFramesRead; + } + + std::string errorAsString(int opusErrorCode) + { + switch(opusErrorCode) + { + case OP_FALSE: return "A request did not succeed"; + case OP_EOF: return "End of File Reached"; + case OP_HOLE: return "There was a hole in the page sequence numbers (e.g., a page was corrupt or missing)."; + case OP_EREAD: return "An underlying read, seek, or tell operation failed when it should have succeeded."; + case OP_EFAULT: return "A NULL pointer was passed where one was unexpected, or an internal memory allocation failed, or an internal library error was encountered."; + case OP_EIMPL: return "The stream used a feature that is not implemented, such as an unsupported channel family. "; + case OP_EINVAL: return "One or more parameters to a function were invalid. "; + case OP_ENOTFORMAT: return "A purported Ogg Opus stream did not begin with an Ogg page, a purported header packet did not start with one of the required strings"; + case OP_EBADHEADER: return "A required header packet was not properly formatted, contained illegal values, or was missing altogether."; + case OP_EVERSION: return "The ID header contained an unrecognized version number."; + case OP_ENOTAUDIO: return "Not Audio"; + case OP_EBADPACKET: return "An audio packet failed to decode properly."; + case OP_EBADLINK: return "We failed to find data we had seen before, or the bitstream structure was sufficiently malformed that seeking to the target destination was impossible."; + case OP_ENOSEEK: return "An operation that requires seeking was requested on an unseekable stream."; + case OP_EBADTIMESTAMP: return "The first or last granule position of a link failed basic validity checks."; + default: return "Unknown Error"; + } + } + + //////////////////// + // opus callbacks // + //////////////////// + +private: + + NO_MOVE(OpusDecoderInternal); + + OggOpusFile * fileHandle; + + AudioData * d; + + inline int64_t getTotalSamples() const { return int64_t(op_pcm_total(const_cast(fileHandle), -1)); } + inline int64_t getLengthInSeconds() const { return uint64_t(getTotalSamples() / OPUS_SAMPLE_RATE); } + inline int64_t getCurrentSample() const { return int64_t(op_pcm_tell(const_cast(fileHandle))); } + +}; + +////////////////////// +// Public Interface // +////////////////////// + +int nqr::OpusDecoder::LoadFromPath(AudioData * data, const std::string & path) +{ + auto fileBuffer = nqr::ReadFile(path); + OpusDecoderInternal decoder(data, fileBuffer.buffer); + return IOError::NoError; +} + +int nqr::OpusDecoder::LoadFromBuffer(AudioData * data, const std::vector & memory) +{ + OpusDecoderInternal decoder(data, memory); + return IOError::NoError; +} + +std::vector nqr::OpusDecoder::GetSupportedFileExtensions() +{ + return {"opus"}; +} \ No newline at end of file diff --git a/src/OpusDependencies.c b/src/OpusDependencies.c new file mode 100644 index 0000000..bbbe611 --- /dev/null +++ b/src/OpusDependencies.c @@ -0,0 +1,258 @@ +#pragma comment(user, "license") + +// https://dxr.mozilla.org/mozilla-central/source/media/libopus + +#if (_MSC_VER) +#pragma warning (push) +#pragma warning (disable: 181 111 4267 4996 4244 4701 4702 4133 4100 4127 4206 4312 4505 4365 4005 4013 4334 4703) +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wconversion" +#pragma clang diagnostic ignored "-Wshadow" +#pragma clang diagnostic ignored "-Wdeprecated-register" +#endif + +#undef HAVE_CONFIG_H + +#define USE_ALLOCA 1 +#define OPUS_BUILD 1 + +/* Enable SSE functions, if compiled with SSE/SSE2 (note that AMD64 implies SSE2) */ +#if defined(_M_X64) || (defined(_M_IX86_FP) && (_M_IX86_FP >= 1)) +#define __SSE__ 1 +#endif + +////////// +// CELT // +////////// + +#include "opus/celt/arch.h" +#include "opus/celt/bands.h" +#include "opus/celt/celt.h" +#include "opus/celt/celt_lpc.h" +#include "opus/celt/cwrs.h" +#include "opus/celt/ecintrin.h" +#include "opus/celt/entcode.h" +#include "opus/celt/entdec.h" +#include "opus/celt/entenc.h" +#include "opus/celt/float_cast.h" +#include "opus/celt/kiss_fft.h" +#include "opus/celt/laplace.h" +#include "opus/celt/mathops.h" +#include "opus/celt/mdct.h" +#include "opus/celt/mfrngcod.h" +#include "opus/celt/modes.h" +#include "opus/celt/os_support.h" +#include "opus/celt/pitch.h" +#include "opus/celt/quant_bands.h" +#include "opus/celt/rate.h" +#include "opus/celt/stack_alloc.h" +#include "opus/celt/vq.h" +#include "opus/celt/_kiss_fft_guts.h" + +#include "opus/celt/bands.c" +#include "opus/celt/celt.c" +#include "opus/celt/celt_lpc.c" +#include "opus/celt/cwrs.c" +#include "opus/celt/entcode.c" +#include "opus/celt/entdec.c" +#include "opus/celt/entenc.c" +#include "opus/celt/kiss_fft.c" +#include "opus/celt/laplace.c" +#include "opus/celt/mathops.c" +#include "opus/celt/mdct.c" +#include "opus/celt/modes.c" +#include "opus/celt/pitch.c" +#include "opus/celt/quant_bands.c" +#include "opus/celt/rate.c" +#include "opus/celt/vq.c" + +// Disabled inline because of name clash of opus_custom_encoder_get_size. +//#include "opus/celt/celt_decoder.c" +//#include "opus/celt/celt_encoder.c" + +/* + See celt/celt_decoder.c + celt/celt_encoder.c in the project browser. + These files need to be in separate translation units due to name clashes, + unfortunately. + */ + +///////////////// +// SILK Common // +///////////////// + +#include "opus_types.h" + +#include "opus/silk/control.h" +#include "opus/silk/debug.h" +#include "opus/silk/define.h" +#include "opus/silk/errors.h" +#include "opus/silk/MacroCount.h" +#include "opus/silk/MacroDebug.h" +#include "opus/silk/macros.h" +#include "opus/silk/main.h" +#include "opus/silk/pitch_est_defines.h" +#include "opus/silk/PLC.h" +#include "opus/silk/resampler_private.h" +#include "opus/silk/resampler_rom.h" +#include "opus/silk/resampler_structs.h" +#include "opus/silk/API.h" +#include "opus/silk/SigProc_FIX.h" +#include "opus/silk/structs.h" +#include "opus/silk/tables.h" +#include "opus/silk/tuning_parameters.h" +#include "opus/silk/typedef.h" + +#include "opus/silk/A2NLSF.c" +#include "opus/silk/ana_filt_bank_1.c" +#include "opus/silk/biquad_alt.c" +#include "opus/silk/bwexpander.c" +#include "opus/silk/bwexpander_32.c" +#include "opus/silk/check_control_input.c" +#include "opus/silk/CNG.c" +#include "opus/silk/code_signs.c" +#include "opus/silk/control_audio_bandwidth.c" +#include "opus/silk/control_codec.c" +#include "opus/silk/control_SNR.c" +#include "opus/silk/debug.c" +#include "opus/silk/decoder_set_fs.c" +#include "opus/silk/decode_core.c" +#include "opus/silk/decode_frame.c" +#include "opus/silk/decode_indices.c" +#include "opus/silk/decode_parameters.c" +#include "opus/silk/decode_pitch.c" +#include "opus/silk/decode_pulses.c" +#include "opus/silk/dec_API.c" +#include "opus/silk/encode_indices.c" +#include "opus/silk/encode_pulses.c" +#include "opus/silk/enc_API.c" +#include "opus/silk/gain_quant.c" +#include "opus/silk/HP_variable_cutoff.c" +#include "opus/silk/init_decoder.c" +#include "opus/silk/init_encoder.c" +#include "opus/silk/inner_prod_aligned.c" +#include "opus/silk/interpolate.c" +#include "opus/silk/lin2log.c" +#include "opus/silk/log2lin.c" +#include "opus/silk/LPC_analysis_filter.c" +#include "opus/silk/LPC_inv_pred_gain.c" +#include "opus/silk/LP_variable_cutoff.c" +#include "opus/silk/NLSF2A.c" +#include "opus/silk/NLSF_decode.c" +#include "opus/silk/NLSF_del_dec_quant.c" +#include "opus/silk/NLSF_encode.c" +#include "opus/silk/NLSF_stabilize.c" +#include "opus/silk/NLSF_unpack.c" +#include "opus/silk/NLSF_VQ.c" +#include "opus/silk/NLSF_VQ_weights_laroia.c" +#include "opus/silk/NSQ.c" +#include "opus/silk/NSQ_del_dec.c" +#include "opus/silk/pitch_est_tables.c" +#include "opus/silk/PLC.c" +#include "opus/silk/process_NLSFs.c" +#include "opus/silk/quant_LTP_gains.c" +#include "opus/silk/resampler.c" +#include "opus/silk/resampler_down2.c" +#include "opus/silk/resampler_down2_3.c" +#include "opus/silk/resampler_private_AR2.c" +#include "opus/silk/resampler_private_down_FIR.c" +#include "opus/silk/resampler_private_IIR_FIR.c" +#include "opus/silk/resampler_private_up2_HQ.c" +#include "opus/silk/resampler_rom.c" +#include "opus/silk/shell_coder.c" +#include "opus/silk/sigm_Q15.c" +#include "opus/silk/sort.c" +#include "opus/silk/stereo_decode_pred.c" +#include "opus/silk/stereo_encode_pred.c" +#include "opus/silk/stereo_find_predictor.c" +#include "opus/silk/stereo_LR_to_MS.c" +#include "opus/silk/stereo_MS_to_LR.c" +#include "opus/silk/stereo_quant_pred.c" +#include "opus/silk/sum_sqr_shift.c" +#include "opus/silk/tables_gain.c" +#include "opus/silk/tables_LTP.c" +#include "opus/silk/tables_NLSF_CB_NB_MB.c" +#include "opus/silk/tables_NLSF_CB_WB.c" +#include "opus/silk/tables_other.c" +#include "opus/silk/tables_pitch_lag.c" +#include "opus/silk/tables_pulses_per_block.c" +#include "opus/silk/table_LSF_cos.c" +#include "opus/silk/VAD.c" +#include "opus/silk/VQ_WMat_EC.c" + +//////////////// +// SILK Float // +//////////////// + +#include "opus/silk/float/main_FLP.h" +#include "opus/silk/float/SigProc_FLP.h" +#include "opus/silk/float/structs_FLP.h" + +#include "opus/silk/float/apply_sine_window_FLP.c" +#include "opus/silk/float/autocorrelation_FLP.c" +#include "opus/silk/float/burg_modified_FLP.c" +#include "opus/silk/float/bwexpander_FLP.c" +#include "opus/silk/float/corrMatrix_FLP.c" +#include "opus/silk/float/encode_frame_FLP.c" +#include "opus/silk/float/energy_FLP.c" +#include "opus/silk/float/find_LPC_FLP.c" +#include "opus/silk/float/find_LTP_FLP.c" +#include "opus/silk/float/find_pitch_lags_FLP.c" +#include "opus/silk/float/find_pred_coefs_FLP.c" +#include "opus/silk/float/inner_product_FLP.c" +#include "opus/silk/float/k2a_FLP.c" +#include "opus/silk/float/levinsondurbin_FLP.c" +#include "opus/silk/float/LPC_analysis_filter_FLP.c" +#include "opus/silk/float/LPC_inv_pred_gain_FLP.c" +#include "opus/silk/float/LTP_analysis_filter_FLP.c" +#include "opus/silk/float/LTP_scale_ctrl_FLP.c" +#include "opus/silk/float/noise_shape_analysis_FLP.c" +#include "opus/silk/float/pitch_analysis_core_FLP.c" +#include "opus/silk/float/prefilter_FLP.c" +#include "opus/silk/float/process_gains_FLP.c" +#include "opus/silk/float/regularize_correlations_FLP.c" +#include "opus/silk/float/residual_energy_FLP.c" +#include "opus/silk/float/scale_copy_vector_FLP.c" +#include "opus/silk/float/scale_vector_FLP.c" +#include "opus/silk/float/schur_FLP.c" +#include "opus/silk/float/solve_LS_FLP.c" +#include "opus/silk/float/sort_FLP.c" +#include "opus/silk/float/warped_autocorrelation_FLP.c" +#include "opus/silk/float/wrappers_FLP.c" + +///////////// +// LibOpus // +///////////// + +#include "opus/libopus/src/opus.c" +#include "opus/libopus/src/opus_decoder.c" +#include "opus/libopus/src/opus_encoder.c" +#include "opus/libopus/src/opus_multistream.c" +#include "opus/libopus/src/opus_multistream_decoder.c" +#include "opus/libopus/src/opus_multistream_encoder.c" +#include "opus/libopus/src/repacketizer.c" + +#include "opus/libopus/src/analysis.c" +#include "opus/libopus/src/mlp.c" +#include "opus/libopus/src/mlp_data.c" + +////////////// +// Opusfile // +////////////// + +#include "opus/opusfile/src/http.c" +#include "opus/opusfile/src/info.c" +#include "opus/opusfile/src/internal.c" +#include "opus/opusfile/src/opusfile.c" +#include "opus/opusfile/src/stream.c" +#include "opus/opusfile/src/wincerts.c" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#if (_MSC_VER) +#pragma warning (pop) +#endif diff --git a/src/VorbisDecoder.cpp b/src/VorbisDecoder.cpp new file mode 100644 index 0000000..b4ccdc7 --- /dev/null +++ b/src/VorbisDecoder.cpp @@ -0,0 +1,174 @@ +#pragma comment(user, "license") + +#include "VorbisDecoder.h" + +using namespace nqr; + +//@todo: implement decoding from memory (c.f. http://stackoverflow.com/questions/13437422/libvorbis-audio-decode-from-memory-in-c) +class VorbisDecoderInternal +{ + +public: + + VorbisDecoderInternal(AudioData * d, std::string filepath) : d(d) + { + fileHandle = new OggVorbis_File(); + + /* @todo proper steaming support + classes + const ov_callbacks callbacks = + { + .read_func = s_readCallback, + .seek_func = s_seekCallback, + .tell_func = s_tellCallback, + .close_func = nullptr + }; + */ + + FILE * f = fopen(filepath.c_str(), "rb"); + + if (!f) + throw std::runtime_error("Can't open file"); + + if (auto r = ov_test_callbacks(f, fileHandle, nullptr, 0, OV_CALLBACKS_DEFAULT) != 0) + { + std::cerr << errorAsString(r) << std::endl; + throw std::runtime_error("File is not a valid ogg vorbis file"); + } + + if (auto r = ov_test_open(fileHandle) != 0) + { + std::cerr << errorAsString(r) << std::endl; + throw std::runtime_error("ov_test_open failed"); + } + // N.B.: Don't need to fclose() after an open -- vorbis does this internally + + vorbis_info *ovInfo = ov_info(fileHandle, -1); + + if (ovInfo == nullptr) + { + throw std::runtime_error("Reading metadata failed"); + } + + if (auto r = ov_streams(fileHandle) != 1) + { + std::cerr << errorAsString(r) << std::endl; + throw std::runtime_error( "Unsupported: file contains multiple bitstreams"); + } + + d->sampleRate = int(ovInfo->rate); + d->channelCount = ovInfo->channels; + d->bitDepth = 32; // float + d->lengthSeconds = double(getLengthInSeconds()); + d->frameSize = ovInfo->channels * d->bitDepth; + + // Samples in a single channel + auto totalSamples = size_t(getTotalSamples()); + + d->samples.resize(totalSamples * d->channelCount); + + auto r = readInternal(totalSamples); + } + + ~VorbisDecoderInternal() + { + ov_clear(fileHandle); + } + + size_t readInternal(size_t requestedFrameCount, size_t frameOffset = 0) + { + + //@todo support offset + + float **buffer = nullptr; + size_t framesRemaining = requestedFrameCount; + size_t totalFramesRead = 0; + int bitstream = 0; + + while(0 < framesRemaining) + { + int64_t framesRead = ov_read_float(fileHandle, &buffer, std::min(2048, (int) framesRemaining), &bitstream); + + // EOF + if(!framesRead) + break; + + if (framesRead < 0) + { + // Probably OV_HOLE, OV_EBADLINK, OV_EINVAL. Log warning here. + continue; + } + + for (int i = 0; i < framesRead; ++i) + { + for(int ch = 0; ch < d->channelCount; ch++) + { + d->samples[totalFramesRead] = buffer[ch][i]; + totalFramesRead++; + } + } + + } + + return totalFramesRead; + } + + std::string errorAsString(int ovErrorCode) + { + switch(ovErrorCode) + { + case OV_FALSE: return "OV_FALSE"; + case OV_EOF: return "OV_EOF"; + case OV_HOLE: return "OV_HOLE"; + case OV_EREAD: return "OV_EREAD"; + case OV_EFAULT: return "OV_EFAULT"; + case OV_EIMPL: return "OV_EIMPL"; + case OV_EINVAL: return "OV_EINVAL"; + case OV_ENOTVORBIS: return "OV_ENOTVORBIS"; + case OV_EBADHEADER: return "OV_EBADHEADER"; + case OV_EVERSION: return "OV_EVERSION"; + case OV_ENOTAUDIO: return "OV_ENOTAUDIO"; + case OV_EBADPACKET: return "OV_EBADPACKET"; + case OV_EBADLINK: return "OV_EBADLINK"; + case OV_ENOSEEK: return "OV_ENOSEEK"; + default: return "OV_UNKNOWN_ERROR"; + } + } + + ////////////////////// + // vorbis callbacks // + ////////////////////// + + //@todo: implement streaming support + +private: + + NO_COPY(VorbisDecoderInternal); + + OggVorbis_File * fileHandle; + AudioData * d; + + inline int64_t getTotalSamples() const { return int64_t(ov_pcm_total(const_cast(fileHandle), -1)); } + inline int64_t getLengthInSeconds() const { return int64_t(ov_time_total(const_cast(fileHandle), -1)); } + inline int64_t getCurrentSample() const { return int64_t(ov_pcm_tell(const_cast(fileHandle))); } + +}; + +////////////////////// +// Public Interface // +////////////////////// + +int VorbisDecoder::LoadFromPath(AudioData * data, const std::string & path) +{ + VorbisDecoderInternal decoder(data, path); + return IOError::NoError; +} + +int VorbisDecoder::LoadFromBuffer(AudioData * data, const std::vector & memory) +{ + return IOError::LoadBufferNotImplemented; +} + +std::vector VorbisDecoder::GetSupportedFileExtensions() +{ + return {"ogg"}; +} diff --git a/src/VorbisDependencies.c b/src/VorbisDependencies.c new file mode 100644 index 0000000..1a152cb --- /dev/null +++ b/src/VorbisDependencies.c @@ -0,0 +1,50 @@ +#pragma comment(user, "license") + +#if (_MSC_VER) +#pragma warning (push) +#pragma warning (disable: 181 111 4267 4996 4244 4701 4702 4133 4100 4127 4206 4312 4505 4365 4005 4013 4334) +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wconversion" +#pragma clang diagnostic ignored "-Wshadow" +#pragma clang diagnostic ignored "-Wdeprecated-register" +#endif + +#include "libvorbis/include/vorbis/vorbisenc.h" +#include "libvorbis/include/vorbis/codec.h" +#include "libvorbis/include/vorbis/vorbisfile.h" + +#include "libogg/src/bitwise.c" +#include "libogg/src/framing.c" + +#include "libvorbis/src/analysis.c" +#include "libvorbis/src/bitrate.c" +#include "libvorbis/src/block.c" +#include "libvorbis/src/codebook.c" +#include "libvorbis/src/envelope.c" +#include "libvorbis/src/floor0.c" +#include "libvorbis/src/floor1.c" +#include "libvorbis/src/info.c" +#include "libvorbis/src/lpc.c" +#include "libvorbis/src/lsp.c" +#include "libvorbis/src/mapping0.c" +#include "libvorbis/src/psy.c" +#include "libvorbis/src/registry.c" +#include "libvorbis/src/res0.c" +#include "libvorbis/src/sharedbook.c" +#include "libvorbis/src/smallft.c" +#include "libvorbis/src/synthesis.c" +#include "libvorbis/src/vorbisenc.c" +#include "libvorbis/src/vorbisfile.c" +#include "libvorbis/src/window.c" +#include "libvorbis/src/mdct.c" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#if (_MSC_VER) +#pragma warning (pop) +#endif diff --git a/src/WavDecoder.cpp b/src/WavDecoder.cpp new file mode 100644 index 0000000..f06782b --- /dev/null +++ b/src/WavDecoder.cpp @@ -0,0 +1,185 @@ +#pragma comment(user, "license") + +#include "WavDecoder.h" + +using namespace nqr; + +////////////////////// +// Public Interface // +////////////////////// + +int WavDecoder::LoadFromPath(AudioData * data, const std::string & path) +{ + auto fileBuffer = nqr::ReadFile(path); + return LoadFromBuffer(data, fileBuffer.buffer); +} + +int WavDecoder::LoadFromBuffer(AudioData * data, const std::vector & memory) +{ + ////////////////////// + // Read RIFF Header // + ////////////////////// + + //@todo swap methods for rifx + + RiffChunkHeader riffHeader = {}; + memcpy(&riffHeader, memory.data(), 12); + + // Files should be 2-byte aligned + //@tofix: enforce this + bool usePaddingShort = ((riffHeader.file_size % sizeof(uint16_t)) == 1) ? true : false; + + // Check RIFF + if (riffHeader.id_riff != GenerateChunkCode('R', 'I', 'F', 'F')) + { + // Check RIFX + FFIR + if (riffHeader.id_riff == GenerateChunkCode('R', 'I', 'F', 'X') || riffHeader.id_riff == GenerateChunkCode('F', 'F', 'I', 'R')) + { + // We're not RIFF, and we don't match RIFX or FFIR either + throw std::runtime_error("libnyquist doesn't support big endian files"); + } + else + { + throw std::runtime_error("bad RIFF/RIFX/FFIR file header"); + } + } + + if (riffHeader.id_wave != GenerateChunkCode('W', 'A', 'V', 'E')) throw std::runtime_error("bad WAVE header"); + + if ((memory.size() - riffHeader.file_size) != sizeof(uint32_t) * 2) + { + throw std::runtime_error("declared size of file less than file size"); //@todo warning instead of runtime_error + } + + ////////////////////// + // Read WAVE Header // + ////////////////////// + + auto WaveChunkInfo = ScanForChunk(memory, GenerateChunkCode('f', 'm', 't', ' ')); + + if (WaveChunkInfo.offset == 0) throw std::runtime_error("couldn't find fmt chunk"); + + assert(WaveChunkInfo.size == 16 || WaveChunkInfo.size == 18 || WaveChunkInfo.size == 40); + + WaveChunkHeader wavHeader = {}; + memcpy(&wavHeader, memory.data() + WaveChunkInfo.offset, sizeof(WaveChunkHeader)); + + if (wavHeader.chunk_size < 16) + throw std::runtime_error("format chunk too small"); + + //@todo validate wav header (sane sample rate, bit depth, etc) + + data->channelCount = wavHeader.channel_count; + data->sampleRate = wavHeader.sample_rate; + data->frameSize = wavHeader.frame_size; + + std::cout << wavHeader << std::endl; + + bool scanForFact = false; + bool grabExtensibleData = false; + + if (wavHeader.format == WaveFormatCode::FORMAT_PCM) + { + std::cout << "[format id] pcm" << std::endl; + } + else if (wavHeader.format == WaveFormatCode::FORMAT_IEEE) + { + std::cout << "[format id] ieee" << std::endl; + scanForFact = true; + } + else if (wavHeader.format == WaveFormatCode::FORMAT_EXT) + { + // Used when (1) PCM data has more than 16 bits; (2) channels > 2; (3) bits/sample !== container size; (4) channel/speaker mapping specified + std::cout << "[format id] extended" << std::endl; + scanForFact = true; + grabExtensibleData = true; + } + else if (wavHeader.format == WaveFormatCode::FORMAT_UNKNOWN) + { + throw std::runtime_error("unknown wave format"); + } + + //////////////////////////// + // Read Additional Chunks // + //////////////////////////// + + if (scanForFact) + { + auto FactChunkInfo = ScanForChunk(memory, GenerateChunkCode('f', 'a', 'c', 't')); + FactChunk factChunk = {}; + + if (FactChunkInfo.size) + { + memcpy(&factChunk, memory.data() + FactChunkInfo.offset, sizeof(FactChunk)); + } + } + + if (grabExtensibleData) + { + ExtensibleData extData = {}; + memcpy(&extData, memory.data() + WaveChunkInfo.offset + sizeof(WaveChunkHeader), sizeof(ExtensibleData)); + // extData can be compared against the multi-channel masks defined in the header + // eg. extData.channel_mask == SPEAKER_5POINT1 + } + + //@todo smpl chunk could be useful + + ///////////////////// + // Read Bext Chunk // + ///////////////////// + + auto BextChunkInfo = ScanForChunk(memory, GenerateChunkCode('b', 'e', 'x', 't')); + BextChunk bextChunk = {}; + + if (BextChunkInfo.size) + { + memcpy(&bextChunk, memory.data() + BextChunkInfo.offset, sizeof(BextChunk)); + } + + ///////////////////// + // Read DATA Chunk // + ///////////////////// + + auto DataChunkInfo = ScanForChunk(memory, GenerateChunkCode('d', 'a', 't', 'a')); + + if (DataChunkInfo.offset == 0) throw std::runtime_error("couldn't find data chunk"); + + DataChunkInfo.offset += 8; // ignore the header and size fields (2 uint32s) + + data->lengthSeconds = ((float) DataChunkInfo.size / (float) wavHeader.sample_rate) / wavHeader.frame_size; + + auto bit_depth = wavHeader.bit_depth; + + size_t totalSamples = (DataChunkInfo.size / wavHeader.frame_size) * wavHeader.channel_count; + data->samples.resize(totalSamples); + + PCMFormat internalFmt = PCMFormat::PCM_END; + + switch (bit_depth) + { + case 8: + internalFmt = PCMFormat::PCM_U8; + break; + case 16: + internalFmt = PCMFormat::PCM_16; + break; + case 24: + internalFmt = PCMFormat::PCM_24; + break; + case 32: + internalFmt = (wavHeader.format == WaveFormatCode::FORMAT_IEEE) ? PCMFormat::PCM_FLT : PCMFormat::PCM_32; + break; + case 64: + internalFmt = (wavHeader.format == WaveFormatCode::FORMAT_IEEE) ? PCMFormat::PCM_DBL : PCMFormat::PCM_64; + break; + } + + ConvertToFloat32(data->samples.data(), memory.data() + DataChunkInfo.offset, totalSamples, internalFmt); + + return IOError::NoError; +} + +std::vector WavDecoder::GetSupportedFileExtensions() +{ + return {"wav", "wave"}; +} diff --git a/src/WavPackDecoder.cpp b/src/WavPackDecoder.cpp new file mode 100644 index 0000000..14a2ecc --- /dev/null +++ b/src/WavPackDecoder.cpp @@ -0,0 +1,152 @@ +#pragma comment(user, "license") + +#include "WavPackDecoder.h" +#include "wavpack.h" + +using namespace nqr; + +class WavPackInternal +{ + +public: + + WavPackInternal(AudioData * d, const std::string path) : d(d) + { + char errorStr[128]; + context = WavpackOpenFileInput(path.c_str(), errorStr, OPEN_WVC | OPEN_NORMALIZE, 0); + + if (!context) + { + throw std::runtime_error("Not a WavPack file"); + } + + d->sampleRate = WavpackGetSampleRate(context); + d->channelCount = WavpackGetNumChannels(context); + d->bitDepth = WavpackGetBitsPerSample(context); + d->lengthSeconds = double(getLengthInSeconds()); + d->frameSize = d->channelCount * d->bitDepth; + + //@todo support channel masks + // WavpackGetChannelMask + + auto totalSamples = size_t(getTotalSamples()); + + PCMFormat internalFmt = PCMFormat::PCM_END; + + int mode = WavpackGetMode(context); + int isFloatingPoint = (MODE_FLOAT & mode); + + switch (d->bitDepth) + { + case 16: + internalFmt = PCMFormat::PCM_16; + break; + case 24: + internalFmt = PCMFormat::PCM_24; + break; + case 32: + internalFmt = isFloatingPoint ? PCMFormat::PCM_FLT : PCMFormat::PCM_32; + break; + default: + throw std::runtime_error("unsupported WavPack bit depth"); + break; + } + + /* From the docs: + "... required memory at "buffer" is 4 * samples * num_channels bytes. The + audio data is returned right-justified in 32-bit longs in the endian + mode native to the executing processor." + */ + d->samples.resize(totalSamples * d->channelCount); + + if (!isFloatingPoint) + internalBuffer.resize(totalSamples * d->channelCount); + + auto r = readInternal(totalSamples); + + // Next, process internal buffer into the user-visible samples array + if (!isFloatingPoint) + ConvertToFloat32(d->samples.data(), internalBuffer.data(), totalSamples * d->channelCount, internalFmt); + + } + + ~WavPackInternal() + { + WavpackCloseFile(context); + } + + size_t readInternal(size_t requestedFrameCount, size_t frameOffset = 0) + { + size_t framesRemaining = requestedFrameCount; + size_t totalFramesRead = 0; + + // int frameSize = d->channelCount * WavpackGetBitsPerSample(context); + + // The samples returned are handled differently based on the file's mode + int mode = WavpackGetMode(context); + + while (0 < framesRemaining) + { + uint32_t framesRead = -1; + + if (MODE_FLOAT & mode) + { + // Since it's float, we can decode directly into our buffer as a huge blob + framesRead = WavpackUnpackSamples(context, reinterpret_cast(&d->samples.data()[0]), uint32_t(d->samples.size() / d->channelCount)); + } + + else if(MODE_LOSSLESS & mode) + { + // Lossless files will be handed off as integers + framesRead = WavpackUnpackSamples(context, internalBuffer.data(), uint32_t(internalBuffer.size() / d->channelCount)); + } + + // EOF + //if (framesRead == 0) + // break; + + std::cout << framesRead << std::endl; + + totalFramesRead += framesRead; + framesRemaining -= framesRead; + } + + return totalFramesRead; + } + +private: + + NO_MOVE(WavPackInternal); + + //WavpackStreamReader streamReader; //@todo: streaming support + + WavpackContext * context; //@todo unique_ptr + + AudioData * d; + + std::vector internalBuffer; + + inline int64_t getTotalSamples() const { return WavpackGetNumSamples(context); } + inline int64_t getLengthInSeconds() const { return getTotalSamples() / WavpackGetSampleRate(context); } + +}; + +////////////////////// +// Public Interface // +////////////////////// + +int WavPackDecoder::LoadFromPath(AudioData * data, const std::string & path) +{ + WavPackInternal decoder(data, path); + return IOError::NoError; +} + +int WavPackDecoder::LoadFromBuffer(AudioData * data, const std::vector & memory) +{ + return IOError::LoadBufferNotImplemented; +} + +std::vector WavPackDecoder::GetSupportedFileExtensions() +{ + return {"wv"}; +}