diff --git a/include/libnyquist/AudioDecoder.h b/include/libnyquist/AudioDecoder.h new file mode 100644 index 0000000..dd7570c --- /dev/null +++ b/include/libnyquist/AudioDecoder.h @@ -0,0 +1,73 @@ +#pragma comment(user, "license") + +#ifndef AUDIO_DECODER_H +#define AUDIO_DECODER_H + +#include "Common.h" +#include +#include + +namespace nqr +{ + +// Individual decoder classes will throw std::exceptions for bad things, +// but NyquistIO implements this enum for high-level error notifications. +enum IOError +{ + NoError, + NoDecodersLoaded, + ExtensionNotSupported, + LoadPathNotImplemented, + LoadBufferNotImplemented, + UnknownError +}; + +struct BaseDecoder +{ + virtual int LoadFromPath(nqr::AudioData * data, const std::string & path) = 0; + virtual int LoadFromBuffer(nqr::AudioData * data, const std::vector & memory) = 0; + virtual std::vector GetSupportedFileExtensions() = 0; + //@todo implement streaming helper methods here +}; + + typedef std::pair> DecoderPair; + +class NyquistIO +{ +public: + + NyquistIO(); + ~NyquistIO(); + + int Load(AudioData * data, const std::string & path); + + bool IsFileSupported(const std::string path) const; + +private: + + std::string ParsePathForExtension(const std::string & path) const; + + std::shared_ptr GetDecoderForExtension(const std::string ext); + + void BuildDecoderTable(); + + template + void AddDecoderToTable(std::shared_ptr decoder) + { + auto supportedExtensions = decoder->GetSupportedFileExtensions(); + + //@todo: basic sanity checking that the extension isn't already supported + for (const auto ext : supportedExtensions) + { + decoderTable.insert(DecoderPair(ext, decoder)); + } + } + + std::map> decoderTable; + + NO_MOVE(NyquistIO); +}; + +} // end namespace nqr + +#endif \ No newline at end of file diff --git a/include/libnyquist/AudioDevice.h b/include/libnyquist/AudioDevice.h new file mode 100644 index 0000000..e94a123 --- /dev/null +++ b/include/libnyquist/AudioDevice.h @@ -0,0 +1,72 @@ +#pragma comment(user, "license") + +#ifndef AUDIO_DEVICE_H +#define AUDIO_DEVICE_H + +// This file implements a simple sound file player based on RtAudio for testing / example purposes. + +#include "Common.h" +#include "RingBuffer.h" + +#include "rtaudio/RtAudio.h" + +#include +#include + +namespace nqr +{ + +const unsigned int FRAME_SIZE = 512; +const int CHANNELS = 2; +const int BUFFER_LENGTH = FRAME_SIZE * CHANNELS; + +struct DeviceInfo +{ + int id; + int numChannels; + int sampleRate; + unsigned int frameSize; + bool isPlaying = false; +}; + +class AudioDevice +{ + + NO_MOVE(AudioDevice); + + std::unique_ptr rtaudio; + +public: + + DeviceInfo info; + + static void ListAudioDevices(); + + AudioDevice(int numChannels, int sampleRate, int deviceId = -1) + { + rtaudio = std::unique_ptr(new RtAudio); + info.id = deviceId != -1 ? deviceId : rtaudio->getDefaultOutputDevice(); + info.numChannels = numChannels; + info.sampleRate = sampleRate; + info.frameSize = FRAME_SIZE; + } + + ~AudioDevice() + { + if (rtaudio) + { + rtaudio->stopStream(); + if (rtaudio->isStreamOpen()) + rtaudio->closeStream(); + } + } + + bool Open(const int deviceId); + + bool Play(const std::vector & data); + +}; + +} // end namespace nqr + +#endif diff --git a/include/libnyquist/CafDecoder.h b/include/libnyquist/CafDecoder.h new file mode 100644 index 0000000..d15a1c3 --- /dev/null +++ b/include/libnyquist/CafDecoder.h @@ -0,0 +1,22 @@ +#pragma comment(user, "license") + +#ifndef CAF_DECODER_H +#define CAF_DECODER_H + +#include "AudioDecoder.h" + +namespace nqr +{ + +struct CAFDecoder : public nqr::BaseDecoder +{ + CAFDecoder() {}; + virtual ~CAFDecoder() {}; + virtual int LoadFromPath(nqr::AudioData * data, const std::string & path) override; + virtual int LoadFromBuffer(nqr::AudioData * data, const std::vector & memory) override; + virtual std::vector GetSupportedFileExtensions() override; +}; + +} // end namespace nqr + +#endif \ No newline at end of file diff --git a/include/libnyquist/Common.h b/include/libnyquist/Common.h new file mode 100644 index 0000000..82e6e98 --- /dev/null +++ b/include/libnyquist/Common.h @@ -0,0 +1,330 @@ +#pragma comment(user, "license") + +#ifndef LIBNYQUIST_COMMON_H +#define LIBNYQUIST_COMMON_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "PostProcess.h" + +namespace nqr +{ + +///////////////// +// Util Macros // +///////////////// + +#define F_ROUND(x) ((x) > 0 ? (x) + 0.5f : (x) - 0.5f) +#define D_ROUND(x) ((x) > 0 ? (x) + 0.5 : (x) - 0.5) + +#define NO_COPY(C) C(const C &) = delete; C & operator = (const C &) = delete +#define NO_MOVE(C) NO_COPY(C); C(C &&) = delete; C & operator = (const C &&) = delete + +/////////////////////// +// Endian Operations // +/////////////////////// + +#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) + #define CPU_X86 1 +#endif + +#if defined(__arm__) || defined(_M_ARM) + #define CPU_ARM 1 +#endif + +#if defined(CPU_X86) && defined(CPU_ARM) + #error CPU_X86 and CPU_ARM both defined. +#endif + +#if !defined(ARCH_CPU_BIG_ENDIAN) && !defined(ARCH_CPU_LITTLE_ENDIAN) + #if CPU_X86 || CPU_ARM || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + #define ARCH_CPU_LITTLE_ENDIAN + #elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + #define ARCH_CPU_BIG_ENDIAN + #else + #error ARCH_CPU_BIG_ENDIAN or ARCH_CPU_LITTLE_ENDIAN should be defined. + #endif +#endif + +#if defined(ARCH_CPU_BIG_ENDIAN) && defined(ARCH_CPU_LITTLE_ENDIAN) + #error ARCH_CPU_BIG_ENDIAN and ARCH_CPU_LITTLE_ENDIAN both defined. +#endif + + + +static inline uint16_t Swap16(uint16_t value) +{ + return (uint16_t)((value >> 8) | (value << 8)); +} + +static inline uint32_t Swap24(uint32_t value) +{ + return (((value & 0x00ff0000) >> 16) | + ((value & 0x0000ff00)) | + ((value & 0x000000ff) << 16)) & 0x00FFFFFF; +} + +static inline uint32_t Swap32(uint32_t value) +{ + return (((value & 0x000000ff) << 24) | + ((value & 0x0000ff00) << 8) | + ((value & 0x00ff0000) >> 8) | + ((value & 0xff000000) >> 24)); +} + +static inline uint64_t Swap64(uint64_t value) +{ + return (((value & 0x00000000000000ffLL) << 56) | + ((value & 0x000000000000ff00LL) << 40) | + ((value & 0x0000000000ff0000LL) << 24) | + ((value & 0x00000000ff000000LL) << 8) | + ((value & 0x000000ff00000000LL) >> 8) | + ((value & 0x0000ff0000000000LL) >> 24) | + ((value & 0x00ff000000000000LL) >> 40) | + ((value & 0xff00000000000000LL) >> 56)); +} + +#ifdef ARCH_CPU_LITTLE_ENDIAN + #define Read16(n) (n) + #define Read24(n) (n) + #define Read32(n) (n) + #define Read64(n) (n) + #define Write16(n) (n) + #define Write24(n) (n) + #define Write32(n) (n) + #define Write64(n) (n) +#else + #define Read16(n) Swap16(n) + #define Read24(n) Swap24(n) + #define Read32(n) Swap32(n) + #define Read64(n) Swap64(n) + #define Write16(n) Swap16(n) + #define Write24(n) Swap24(n) + #define Write32(n) Swap32(n) + #define Write64(n) Swap64(n) +#endif + +inline uint64_t Pack(uint32_t a, uint32_t b) +{ + uint64_t tmp = (uint64_t) b << 32 | (uint64_t) a; +#ifdef ARCH_CPU_LITTLE_ENDIAN + return tmp; +#else + return Swap64(tmp); +#endif +} + +inline uint32_t Pack(uint16_t a, uint16_t b) +{ + uint32_t tmp = (uint32_t) b << 16 | (uint32_t) a; +#ifdef ARCH_CPU_LITTLE_ENDIAN + return tmp; +#else + return Swap32(tmp); +#endif +} + +inline uint16_t Pack(uint8_t a, uint8_t b) +{ + uint16_t tmp = (uint16_t) b << 8 | (uint16_t) a; +#ifdef ARCH_CPU_LITTLE_ENDIAN + return tmp; +#else + return Swap16(tmp); +#endif +} + +// http://www.dsprelated.com/showthread/comp.dsp/136689-1.php +inline int32_t Pack(uint8_t a, uint8_t b, uint8_t c) +{ + // uint32_t tmp = ((c & 0x80) ? (0xFF << 24) : 0x00 << 24) | (c << 16) | (b << 8) | (a << 0); // alternate method + int32_t x = (c << 16) | (b << 8) | (a << 0); + auto sign_extended = (x) | (!!((x) & 0x800000) * 0xff000000); + #ifdef ARCH_CPU_LITTLE_ENDIAN + return sign_extended; + #else + Swap32(sign_extended); + #endif +} + +////////////////////////// +// Conversion Utilities // +////////////////////////// + +// Signed maxes, defined for readabilty/convenience +#define NQR_INT16_MAX 32768.f +#define NQR_INT24_MAX 8388608.f +#define NQR_INT32_MAX 2147483648.f + +static const float NQR_BYTE_2_FLT = 1.0f / 127.0f; + +#define int8_to_float32(s) ( (float) (s) * NQR_BYTE_2_FLT) +#define uint8_to_float32(s) ( ((float) (s) - 128) * NQR_BYTE_2_FLT) +#define int16_to_float32(s) ( (float) (s) / NQR_INT16_MAX) +#define int24_to_float32(s) ( (float) (s) / NQR_INT24_MAX) +#define int32_to_float32(s) ( (float) (s) / NQR_INT32_MAX) + +//@todo add 12, 20 for flac +enum PCMFormat +{ + PCM_U8, + PCM_S8, + PCM_16, + PCM_24, + PCM_32, + PCM_64, + PCM_FLT, + PCM_DBL, + PCM_END +}; + +// Src data is aligned to PCMFormat +// @todo normalize? +inline void ConvertToFloat32(float * dst, const uint8_t * src, const size_t N, PCMFormat f) +{ + assert(f != PCM_END); + + if (f == PCM_U8) + { + const uint8_t * dataPtr = reinterpret_cast(src); + for (size_t i = 0; i < N; ++i) + dst[i] = uint8_to_float32(dataPtr[i]); + } + else if (f == PCM_S8) + { + const int8_t * dataPtr = reinterpret_cast(src); + for (size_t i = 0; i < N; ++i) + dst[i] = int8_to_float32(dataPtr[i]); + } + else if (f == PCM_16) + { + const int16_t * dataPtr = reinterpret_cast(src); + for (size_t i = 0; i < N; ++i) + dst[i] = int16_to_float32(Read16(dataPtr[i])); + } + else if (f == PCM_24) + { + const uint8_t * dataPtr = reinterpret_cast(src); + size_t c = 0; + for (size_t i = 0; i < N; ++i) + { + int32_t sample = Pack(dataPtr[c], dataPtr[c+1], dataPtr[c+2]); + dst[i] = int24_to_float32(sample); // Packed types don't need addtional endian helpers + c += 3; + } + } + else if (f == PCM_32) + { + const int32_t * dataPtr = reinterpret_cast(src); + for (size_t i = 0; i < N; ++i) + dst[i] = int32_to_float32(Read32(dataPtr[i])); + } + + //@todo add int64 format + + else if (f == PCM_FLT) + { + const float * dataPtr = reinterpret_cast(src); + for (size_t i = 0; i < N; ++i) + dst[i] = (float) Read32(dataPtr[i]); + } + else if (f == PCM_DBL) + { + const double * dataPtr = reinterpret_cast(src); + for (size_t i = 0; i < N; ++i) + dst[i] = (float) Read64(dataPtr[i]); + } +} + +// Src data is always aligned to 4 bytes (WavPack, primarily) +inline void ConvertToFloat32(float * dst, const int32_t * src, const size_t N, PCMFormat f) +{ + assert(f != PCM_END); + + if (f == PCM_16) + { + for (size_t i = 0; i < N; ++i) + dst[i] = int16_to_float32(Read32(src[i])); + } + else if (f == PCM_24) + { + const uint8_t * dataPtr = reinterpret_cast(src); + size_t c = 0; + for (size_t i = 0; i < N; ++i) + { + int32_t sample = Pack(dataPtr[c], dataPtr[c+1], dataPtr[c+2]); + dst[i] = int24_to_float32(sample); + c += 4; // +4 for next 4 byte boundary + } + } + else if (f == PCM_32) + { + for (size_t i = 0; i < N; ++i) + dst[i] = int32_to_float32(Read32(src[i])); + } +} + +/////////////////////////////////////////// +// Chunk utilities (move somewhere else) // +/////////////////////////////////////////// + +struct ChunkHeaderInfo +{ + uint32_t offset; // Byte offset into chunk + uint32_t size; // Size of the chunk in bytes +}; + +inline uint32_t GenerateChunkCode(uint8_t a, uint8_t b, uint8_t c, uint8_t d) +{ + #ifdef ARCH_CPU_LITTLE_ENDIAN + return ((uint32_t) ((a) | ((b) << 8) | ((c) << 16) | (((uint32_t) (d)) << 24))); + #else + return ((uint32_t) ((((uint32_t) (a)) << 24) | ((b) << 16) | ((c) << 8) | (d))); + #endif +} + +ChunkHeaderInfo ScanForChunk(const std::vector & fileData, uint32_t chunkMarker); + +////////////////////////// +// User Data + File Ops // +////////////////////////// + +struct AudioData +{ + int channelCount; + int sampleRate; + int bitDepth; + double lengthSeconds; + size_t frameSize; // channels * bits per sample + std::vector samples; + //@todo: add field: channel layout + //@todo: add field: lossy / lossless + //@todo: audio data loaded (for metadata only) + //@todo: bitrate (if applicable) + //@todo: original sample rate (if applicable) +}; + +struct StreamableAudioData : public AudioData +{ + //@todo: add field: is this format streamable? + //@todo: hold file handle +}; + +struct NyquistFileBuffer +{ + std::vector buffer; + size_t size; +}; + +NyquistFileBuffer ReadFile(std::string pathToFile); + +} // end namespace nqr + +#endif diff --git a/include/libnyquist/Dither.h b/include/libnyquist/Dither.h new file mode 100644 index 0000000..02fe0fe --- /dev/null +++ b/include/libnyquist/Dither.h @@ -0,0 +1,15 @@ +#pragma comment(user, "license") + +#ifndef DITHER_OPERATIONS_H +#define DITHER_OPERATIONS_H + +#include "Common.h" + +//@todo libnyquist will, at some point, allow conversion between arbitrary bit-depths + +namespace nqr +{ + +} // end namespace nqr + +#endif diff --git a/include/libnyquist/FlacDecoder.h b/include/libnyquist/FlacDecoder.h new file mode 100644 index 0000000..5cf816d --- /dev/null +++ b/include/libnyquist/FlacDecoder.h @@ -0,0 +1,39 @@ +#pragma comment(user, "license") + +#ifndef FLAC_DECODER_H +#define FLAC_DECODER_H + +#include "AudioDecoder.h" +#include + +namespace nqr +{ + +//@todo expose this in API +inline std::map GetQualityTable() +{ + return { + { 0, "0 (Fastest)" }, + { 1, "1" }, + { 2, "2" }, + { 3, "3" }, + { 4, "4" }, + { 5, "5 (Default)" }, + { 6, "6" }, + { 7, "7" }, + { 8, "8 (Highest)" }, + }; + } + +struct FlacDecoder : public nqr::BaseDecoder +{ + FlacDecoder() {} + virtual ~FlacDecoder() {} + virtual int LoadFromPath(nqr::AudioData * data, const std::string & path) override; + virtual int LoadFromBuffer(nqr::AudioData * data, const std::vector & memory) override; + virtual std::vector GetSupportedFileExtensions() override; +}; + +} // end namespace nqr + +#endif diff --git a/include/libnyquist/NickPrototypes.h b/include/libnyquist/NickPrototypes.h new file mode 100644 index 0000000..319a4fe --- /dev/null +++ b/include/libnyquist/NickPrototypes.h @@ -0,0 +1,257 @@ +#pragma comment(user, "license") + +#include "Common.h" + +/* + + AudioSampleBuffer can be backed by a memory store such as standard vector, + + a file, such as NyquistFileBuffer, or some platform specific DMA backed + + thingamajig, or a memmaped file. + + */ + + +template + +struct AudioSampleBuffer + +{ + + // RAII object to temporarily map a data buffer into R/O memory + + struct Data + + { + + Data(AudioSampleBuffer&); // map data into CPU memory + + ~Data(); // unmap data + + + + void *data(); + + size_t size(); + + }; + + + // RAII object to temporarily map a data buffer into R/W memory + + struct MutableData + + { + + MutableData(AudioSampleBuffer&); // map data into CPU memory + + ~MutableData(); // unmap data after copying contents back to backing store + + + + void *data(); + + size_t size(); + + }; + + + + AudioSampleBuffer(std::function resize, + + std::function dealloc) + + : resize(resize), dealloc(dealloc) + + { + + } + + + + ~AudioSampleBuffer() + + { + + if (dealloc) + + dealloc(); + + } + + + + size_t resize(size_t sz) + + { + + if (resize) + + return resize(sz, &buffer); + + + return buffer.size(); + + } + + + +private: + + AudioSampleBuffer() = 0; + + + + Buffer buffer; + + std::function _resize; + + std::function _dealloc; + +}; + + + +struct AudioData + +{ + + AudioData() : channelCount(0), sampleRate(0), bitDepth(0), lengthSeconds(0), frameSize(0) { } + + + + AudioData(const AudioData& rhs) + + : channelCount(rhs.channelCount), sampleRate(rhs.sampleRate), bitDepth(rhs.bitDepth) + + , lengthInSeconds(rhs.lengthInSeconds), frameSize(rhs.frameSize), samples(rhs.samples) { } + + + + int channelCount; // unsigned? + + int sampleRate; // ditto? + + int bitDepth; // ditto? + + double lengthSeconds; + + + + // since it does make sense to set this (does it?) it should probably be a function not a data value + + size_t frameSize; // channels * bits per sample + + + AudioSampleBuffer samples; + + //@todo: add field: streamable + + //@todo: add field: channel layout + + //@todo: add field: lossy / lossless + + + + void mixToChannels(int channelCount); + + void resample(int sampleRate); + + void setBitDepth(int bitDepth); + + void setLength(double lengthInSeconds); + +}; + + + +// in general nice to pass string ref + + + +void LoadFile(AudioData * dataResult, const std::string & path) + +{ + + string extension = path.substr(path.rfind('.') + 1, path.length()); + + if (!extension.length()) + + return; + + + + if (extension == "ogg") + + { + + VorbisDecoder::LoadFile(dataResult, path); + + } + + else if (extension == "wav") + + { + + VorbisDecoder::LoadFile(dataResult, path); + + } + + else + + { + + // etc. + + } + +} + + +void LoadFile(AudioData * dataResult, const std::string & path, bool conformToData) + +{ + + if (!conformToData) + + { + + LoadFile(dataResult, path); + + } + + else + + { + + AudioData * conform = new dataResult(dataResult); // add copy constructor + + LoadFile(dataResult, path); + + + if (conform->channelCount && (conform->channelCount != dataResult->channelCount)) + + dataResult->mixToChannels(conform->channelCount); + + + + if (conform->sampleRate && (conform->sampleRate != dataResult->sampleRate)) + + dataResult->resample(conform->sampleRate); + + + + if (conform->bitDepth && (conform->bitDepth != dataResult->bitDepth)) + + dataResult->setBitDepth(conform->bitDepth); + + + + if (conform->lengthInSeconds > 0 && (conform->lengthSeconds != dataResult->lengthSeconds)) + + dataResult->setLength(conform->lengthSeconds); + + } + +} \ No newline at end of file diff --git a/include/libnyquist/OpusDecoder.h b/include/libnyquist/OpusDecoder.h new file mode 100644 index 0000000..57125ad --- /dev/null +++ b/include/libnyquist/OpusDecoder.h @@ -0,0 +1,28 @@ +#pragma comment(user, "license") + +#ifndef OPUS_DECODER_H +#define OPUS_DECODER_H + +#include "AudioDecoder.h" +#include "opus/opusfile/include/opusfile.h" + +namespace nqr +{ + +// Opus is a general-purpose codec designed to replace Vorbis at some point. Primarily, it's a low +// delay format making it suitable for high-quality, real time streaming. It's not really +// an archival format or something designed for heavy DSP post-processing since +// it's fundamentally limited to encode/decode at 48khz. +// https://mf4.xiph.org/jenkins/view/opus/job/opusfile-unix/ws/doc/html/index.html +struct OpusDecoder : public nqr::BaseDecoder +{ + OpusDecoder() {} + virtual ~OpusDecoder() {} + virtual int LoadFromPath(nqr::AudioData * data, const std::string & path) override; + virtual int LoadFromBuffer(nqr::AudioData * data, const std::vector & memory) override; + virtual std::vector GetSupportedFileExtensions() override; +}; + +} // end namespace nqr + +#endif diff --git a/include/libnyquist/PostProcess.h b/include/libnyquist/PostProcess.h new file mode 100644 index 0000000..d2105c1 --- /dev/null +++ b/include/libnyquist/PostProcess.h @@ -0,0 +1,62 @@ +#pragma comment(user, "license") + +#ifndef POSTPROCESS_H +#define POSTPROCESS_H + +#include + +namespace nqr +{ + +template +inline void DeinterleaveStereo(T * c1, T * c2, T const * src, size_t count) +{ + auto src_end = src + count; + while (src != src_end) + { + *c1 = src[0]; + *c2 = src[1]; + c1++; + c2++; + src += 2; + } +} + +template +void InterleaveArbitrary(const T * src, T * dest, size_t numFramesPerChannel, size_t numChannels, size_t numCopyFrames) +{ + for (size_t ch = 0; ch < numChannels; ch++) + { + size_t x = ch; + const T * srcChannel = &src[ch * numFramesPerChannel]; + for(size_t i = 0; i < numCopyFrames; i++) + { + dest[x] = srcChannel[i]; + x += numChannels; + } + } +} + +template +void DeinterleaveArbitrary(const T * src, T * dest, size_t numFramesPerChannel, size_t numChannels, size_t numCopyFrames) +{ + for(size_t ch = 0; ch < numChannels; ch++) + { + size_t x = ch; + T *destChannel = &dest[ch * numFramesPerChannel]; + for (size_t i = 0; i < numCopyFrames; i++) + { + destChannel[i] = (T) src[x]; + x += numChannels; + } + } +} + +inline void TrimSilenceInterleaved(std::vector & buffer, float v, bool fromFront, bool fromEnd) +{ + //@todo implement me +} + +} // end namespace nqr + +#endif diff --git a/include/libnyquist/RingBuffer.h b/include/libnyquist/RingBuffer.h new file mode 100644 index 0000000..9e579d5 --- /dev/null +++ b/include/libnyquist/RingBuffer.h @@ -0,0 +1,184 @@ +/* + Copyright (c) 2014, The Cinder Project + + This code is intended to be used with the Cinder C++ library, http://libcinder.org + + Redistribution and use in source and binary forms, with or without modification, are permitted provided that + the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and + the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + the following disclaimer in the documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +//! \brief Ringbuffer (aka circular buffer) data structure for use in concurrent audio scenarios. +//! +//! Other than minor modifications, this ringbuffer is a copy of Tim Blechmann's fine work, found as the base +//! structure of boost::lockfree::spsc_queue (ringbuffer_base). Whereas the boost::lockfree data structures +//! are meant for a wide range of applications / archs, this version specifically caters to audio processing. +//! +//! The implementation remains lock-free and thread-safe within a single write thread / single read thread context. + +#pragma once + +#include +#include + +template +class RingBufferT { + public: + //! Constructs a RingBufferT with size = 0 + RingBufferT() : mData( nullptr ), mAllocatedSize( 0 ), mWriteIndex( 0 ), mReadIndex( 0 ) {} + //! Constructs a RingBufferT with \a count maximum elements. + RingBufferT( size_t count ) : mAllocatedSize( 0 ) + { + resize( count ); + } + + RingBufferT( RingBufferT &&other ) + : mData( other.mData ), mAllocatedSize( other.mAllocatedSize ), mWriteIndex( 0 ), mReadIndex( 0 ) + { + other.mData = nullptr; + other.mAllocatedSize = 0; + } + + ~RingBufferT() + { + if( mData ) + free( mData ); + } + //! Resizes the container to contain \a count maximum elements. Invalidates the internal buffer and resets read / write indices to 0. \note Must be synchronized with both read and write threads. + void resize( size_t count ) + { + size_t allocatedSize = count + 1; // one bin is used to distinguish between the read and write indices when full. + + if( mAllocatedSize ) + mData = (T *)::realloc( mData, allocatedSize * sizeof( T ) ); + else + mData = (T *)::calloc( allocatedSize, sizeof( T ) ); + + assert( mData ); + + mAllocatedSize = allocatedSize; + clear(); + } + //! Invalidates the internal buffer and resets read / write indices to 0. \note Must be synchronized with both read and write threads. + void clear() + { + mWriteIndex = 0; + mReadIndex = 0; + } + //! Returns the maximum number of elements. + size_t getSize() const + { + return mAllocatedSize - 1; + } + //! Returns the number of elements available for wrtiing. \note Only safe to call from the write thread. + size_t getAvailableWrite() const + { + return getAvailableWrite( mWriteIndex, mReadIndex ); + } + //! Returns the number of elements available for wrtiing. \note Only safe to call from the read thread. + size_t getAvailableRead() const + { + return getAvailableRead( mWriteIndex, mReadIndex ); + } + + //! \brief Writes \a count elements into the internal buffer from \a array. \return `true` if all elements were successfully written, or `false` otherwise. + //! + //! \note only safe to call from the write thread. + //! TODO: consider renaming this to writeAll / readAll, and having generic read / write that just does as much as it can + bool write( const T *array, size_t count ) + { + const size_t writeIndex = mWriteIndex.load( std::memory_order_relaxed ); + const size_t readIndex = mReadIndex.load( std::memory_order_acquire ); + + if( count > getAvailableWrite( writeIndex, readIndex ) ) + return false; + + size_t writeIndexAfter = writeIndex + count; + + if( writeIndex + count > mAllocatedSize ) { + size_t countA = mAllocatedSize - writeIndex; + size_t countB = count - countA; + + std::memcpy( mData + writeIndex, array, countA * sizeof( T ) ); + std::memcpy( mData, array + countA, countB * sizeof( T ) ); + writeIndexAfter -= mAllocatedSize; + } + else { + std::memcpy( mData + writeIndex, array, count * sizeof( T ) ); + if( writeIndexAfter == mAllocatedSize ) + writeIndexAfter = 0; + } + + mWriteIndex.store( writeIndexAfter, std::memory_order_release ); + return true; + } + //! \brief Reads \a count elements from the internal buffer into \a array. \return `true` if all elements were successfully read, or `false` otherwise. + //! + //! \note only safe to call from the read thread. + bool read( T *array, size_t count ) + { + const size_t writeIndex = mWriteIndex.load( std::memory_order_acquire ); + const size_t readIndex = mReadIndex.load( std::memory_order_relaxed ); + + if( count > getAvailableRead( writeIndex, readIndex ) ) + return false; + + size_t readIndexAfter = readIndex + count; + + if( readIndex + count > mAllocatedSize ) { + size_t countA = mAllocatedSize - readIndex; + size_t countB = count - countA; + + std::memcpy( array, mData + readIndex, countA * sizeof( T ) ); + std::memcpy( array + countA, mData, countB * sizeof( T ) ); + + readIndexAfter -= mAllocatedSize; + } + else { + std::memcpy( array, mData + readIndex, count * sizeof( T ) ); + if( readIndexAfter == mAllocatedSize ) + readIndexAfter = 0; + } + + mReadIndex.store( readIndexAfter, std::memory_order_release ); + return true; + } + + private: + + size_t getAvailableWrite( size_t writeIndex, size_t readIndex ) const + { + size_t result = readIndex - writeIndex - 1; + if( writeIndex >= readIndex ) + result += mAllocatedSize; + + return result; + } + + size_t getAvailableRead( size_t writeIndex, size_t readIndex ) const + { + if( writeIndex >= readIndex ) + return writeIndex - readIndex; + + return writeIndex + mAllocatedSize - readIndex; + } + + T *mData; + size_t mAllocatedSize; + std::atomic mWriteIndex, mReadIndex; +}; + +typedef RingBufferT RingBuffer; diff --git a/include/libnyquist/VorbisDecoder.h b/include/libnyquist/VorbisDecoder.h new file mode 100644 index 0000000..420d039 --- /dev/null +++ b/include/libnyquist/VorbisDecoder.h @@ -0,0 +1,22 @@ +#pragma comment(user, "license") + +#ifndef VORBIS_DECODER_H +#define VORBIS_DECODER_H + +#include "AudioDecoder.h" +#include "libvorbis/include/vorbis/vorbisfile.h" + +namespace nqr +{ + +struct VorbisDecoder : public nqr::BaseDecoder +{ + VorbisDecoder() {} + virtual ~VorbisDecoder() {} + virtual int LoadFromPath(nqr::AudioData * data, const std::string & path) override; + virtual int LoadFromBuffer(nqr::AudioData * data, const std::vector & memory) override; + virtual std::vector GetSupportedFileExtensions() override; +}; + +} // end namespace nqr +#endif \ No newline at end of file diff --git a/include/libnyquist/WavDecoder.h b/include/libnyquist/WavDecoder.h new file mode 100644 index 0000000..d4e7fc6 --- /dev/null +++ b/include/libnyquist/WavDecoder.h @@ -0,0 +1,164 @@ +#pragma comment(user, "license") + +#ifndef WAVE_DECODER_H +#define WAVE_DECODER_H + +#include "AudioDecoder.h" + +namespace nqr +{ + +enum WaveFormatCode +{ + FORMAT_UNKNOWN = 0x0, // Unknown Wave Format + FORMAT_PCM = 0x1, // PCM Format + FORMAT_IEEE = 0x3, // IEEE float/double + FORMAT_ALAW = 0x6, // 8-bit ITU-T G.711 A-law + FORMAT_MULAW = 0x7, // 8-bit ITU-T G.711 µ-law + FORMAT_EXT = 0xFFFE // Set via subformat +}; + +struct RiffChunkHeader +{ + uint32_t id_riff; // Chunk ID: 'RIFF' + uint32_t file_size; // Entire file in bytes + uint32_t id_wave; // Chunk ID: 'WAVE' +}; + +struct WaveChunkHeader +{ + uint32_t fmt_id; // Chunk ID: 'fmt ' + uint32_t chunk_size; // Size in bytes + uint16_t format; // Format code + uint16_t channel_count; // Num interleaved channels + uint32_t sample_rate; // SR + uint32_t data_rate; // Data rate + uint16_t frame_size; // 1 frame = channels * bits per sample + uint16_t bit_depth; // Bits per sample +}; + +struct BextChunk +{ + uint32_t fmt_id; // Chunk ID: 'bext' + uint32_t chunk_size; // Size in bytes + uint8_t description[256]; // Description of the sound (ascii) + uint8_t origin[32]; // Name of the originator (ascii) + uint8_t origin_ref[32]; // Reference of the originator (ascii) + uint8_t orgin_date[10]; // yyyy-mm-dd (ascii) + uint8_t origin_time[8]; // hh-mm-ss (ascii) + uint64_t time_ref; // First sample count since midnight + uint32_t version; // Version of the BWF + uint8_t uimd[64]; // Byte 0 of SMPTE UMID + uint8_t reserved[188]; // 190 bytes, reserved for future use & set to NULL +}; + +struct FactChunk +{ + uint32_t fact_id; // Chunk ID: 'fact' + uint32_t chunk_size; // Size in bytes + uint32_t sample_length; // number of samples per channel +}; + +struct ExtensibleData +{ + uint16_t size; + uint16_t valid_bits_per_sample; + uint32_t channel_mask; + struct GUID + { + uint32_t data0; + uint16_t data1; + uint16_t data2; + uint16_t data3; + uint8_t data4[6]; + }; +}; + +template +std::basic_ostream & operator << (std::basic_ostream & a, const WaveChunkHeader & b) +{ + return a << + "Format ID:\t\t" << b.fmt_id << + "\nChunk Size:\t\t" << b.chunk_size << + "\nFormat Code:\t\t" << b.format << + "\nChannels:\t\t" << b.channel_count << + "\nSample Rate:\t\t" << b.sample_rate << + "\nData Rate:\t\t" << b.data_rate << + "\nFrame Size:\t\t" << b.frame_size << + "\nBit Depth:\t\t" << b.bit_depth << std::endl; +} + +//@todo expose speaker/channel/layout masks in the API: + +enum SpeakerChannelMask +{ + SPEAKER_FRONT_LEFT = 0x00000001, + SPEAKER_FRONT_RIGHT = 0x00000002, + SPEAKER_FRONT_CENTER = 0x00000004, + SPEAKER_LOW_FREQUENCY = 0x00000008, + SPEAKER_BACK_LEFT = 0x00000010, + SPEAKER_BACK_RIGHT = 0x00000020, + SPEAKER_FRONT_LEFT_OF_CENTER = 0x00000040, + SPEAKER_FRONT_RIGHT_OF_CENTER = 0x00000080, + SPEAKER_BACK_CENTER = 0x00000100, + SPEAKER_SIDE_LEFT = 0x00000200, + SPEAKER_SIDE_RIGHT = 0x00000400, + SPEAKER_TOP_CENTER = 0x00000800, + SPEAKER_TOP_FRONT_LEFT = 0x00001000, + SPEAKER_TOP_FRONT_CENTER = 0x00002000, + SPEAKER_TOP_FRONT_RIGHT = 0x00004000, + SPEAKER_TOP_BACK_LEFT = 0x00008000, + SPEAKER_TOP_BACK_CENTER = 0x00010000, + SPEAKER_TOP_BACK_RIGHT = 0x00020000, + SPEAKER_RESERVED = 0x7FFC0000, + SPEAKER_ALL = 0x80000000 +}; + +enum SpeakerLayoutMask +{ + SPEAKER_MONO = (SPEAKER_FRONT_CENTER), + SPEAKER_STEREO = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT), + SPEAKER_2POINT1 = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY), + SPEAKER_SURROUND = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_CENTER), + SPEAKER_QUAD = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT), + SPEAKER_4POINT1 = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT), + SPEAKER_5POINT1 = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT), + SPEAKER_7POINT1 = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER), + SPEAKER_5POINT1_SURROUND = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT), + SPEAKER_7POINT1_SURROUND = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT), +}; + +//@todo verify mask values +inline int ComputeChannelMask(const size_t channels) +{ + switch (channels) + { + case 1: + return SPEAKER_MONO; + case 2: + return SPEAKER_STEREO; + case 3: + return SPEAKER_2POINT1; + case 4: + return SPEAKER_QUAD; + case 5: + return SPEAKER_4POINT1; + case 6: + return SPEAKER_5POINT1; + default: + return -1; + } +} + +struct WavDecoder : public nqr::BaseDecoder +{ + WavDecoder() {} + virtual ~WavDecoder() {} + virtual int LoadFromPath(nqr::AudioData * data, const std::string & path) override; + virtual int LoadFromBuffer(nqr::AudioData * data, const std::vector & memory) override; + virtual std::vector GetSupportedFileExtensions() override; +}; + +} // end namespace nqr + +#endif diff --git a/include/libnyquist/WavPackDecoder.h b/include/libnyquist/WavPackDecoder.h new file mode 100644 index 0000000..efcece6 --- /dev/null +++ b/include/libnyquist/WavPackDecoder.h @@ -0,0 +1,22 @@ +#pragma comment(user, "license") + +#ifndef WAVEPACK_DECODER_H +#define WAVEPACK_DECODER_H + +#include "AudioDecoder.h" + +namespace nqr +{ + +struct WavPackDecoder : public nqr::BaseDecoder +{ + WavPackDecoder() {}; + virtual ~WavPackDecoder() {}; + virtual int LoadFromPath(nqr::AudioData * data, const std::string & path) override; + virtual int LoadFromBuffer(nqr::AudioData * data, const std::vector & memory) override; + virtual std::vector GetSupportedFileExtensions() override; +}; + +} // end namespace nqr + +#endif