Add sound file decoding support with libsndfile

This commit is contained in:
rexy712 2020-08-18 09:11:58 -07:00
parent 09ba2d297c
commit 03693c6d06
10 changed files with 456 additions and 37 deletions

68
include/audio/error.hpp Normal file
View File

@ -0,0 +1,68 @@
/**
This file is a part of the WyZ project
Copyright (C) 2020 rexy712
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OUR_DICK_AUDIO_ERROR_HPP
#define OUR_DICK_AUDIO_ERROR_HPP
namespace audio{
class error
{
public:
enum err{
NONE = 0,
UNSUPPORTED_FORMAT,
SYSTEM,
BAD_FILE,
UNSUPPORTED_ENCODING,
INVALID_CHANNEL_COUNT,
INVALID_SAMPLE_RATE,
INVALID_DEVICE,
INVALID_FLAG,
BAD_DEVICE_COMBO,
OUT_OF_MEMORY,
BUFFER_TOO_BIG,
BUFFER_TOO_SMALL,
BAD_CALLBACK,
BAD_STREAM,
TIMED_OUT,
STREAM_STOPPED,
STREAM_NOT_STOPPED,
INPUT_OVERFLOW,
OUTPUT_UNDERFLOW,
BAD_BUFFER,
};
private:
int m_actual;
int m_homog;
public:
error(int actual);
error(const error&) = default;
error(error&&) = default;
~error() = default;
error& operator=(const error&) = default;
error& operator=(error&&) = default;
operator int()const;
int get()const;
int get_raw()const;
};
}
#endif

View File

@ -0,0 +1,32 @@
/**
This file is a part of our_dick
Copyright (C) 2020 rexy712
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OUR_DICK_AUDIO_FRAME_FMT_HPP
#define OUR_DICK_AUDIO_FRAME_FMT_HPP
#include <portaudio.h>
namespace audio{
enum class frame_fmt{
float32 = paFloat32,
};
}
#endif

View File

@ -23,9 +23,12 @@ namespace audio{
class pa_system
{
public:
private:
pa_system();
~pa_system();
public:
static pa_system& instance();
};
}

90
include/audio/sndrd.hpp Normal file
View File

@ -0,0 +1,90 @@
/**
This file is a part of our_dick
Copyright (C) 2020 rexy712
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OUR_DICK_SNDRD_HPP
#define OUR_DICK_SNDRD_HPP
#include <sndfile.h>
#include <cstddef> //size_t
#include <type_traits>
#include "error.hpp"
namespace audio{
class sndrd
{
private:
SF_INFO m_info = {};
SNDFILE* m_fp = nullptr;
public:
enum class mode : int{
r = SFM_READ,
w = SFM_WRITE,
rw = SFM_RDWR,
};
public:
constexpr sndrd(void)noexcept = default;
sndrd(const char* f, mode m = mode::r)noexcept;
sndrd(const sndrd&) = delete;
sndrd(sndrd&& f)noexcept;
~sndrd(void)noexcept;
sndrd& operator=(const sndrd&) = delete;
sndrd& operator=(sndrd&& f)noexcept;
SNDFILE* release(void)noexcept;
//Returns length in frames
size_t length(void)noexcept;
//Returns position in frames
size_t position(void)const noexcept;
void set_pos(size_t newpos)noexcept;
void seek(long int offset)noexcept;
operator SNDFILE*(void)noexcept;
operator const SNDFILE*(void)const noexcept;
SNDFILE* get(void)noexcept;
const SNDFILE* get(void)const noexcept;
bool valid()const noexcept;
size_t read(float* dest, size_t items)noexcept;
size_t read_frames(float* dest, size_t frames)noexcept;
//TODO other formats
size_t write(const float* src, size_t items)noexcept;
size_t write_frames(const float* src, size_t frames)noexcept;
//TODO other formats
void close()noexcept;
//getters
size_t frames()const noexcept;
int samplerate()const noexcept;
int channels()const noexcept;
int file_format()const noexcept;
int sections()const noexcept;
bool seekable()const noexcept;
error last_error()const noexcept;
};
}
#endif

View File

@ -24,44 +24,41 @@
#include "detail/utility.hpp"
#include "detail.hpp"
#include "error.hpp"
namespace audio{
enum class stream_fmt{
float32 = paFloat32,
};
class stream
{
private:
detail::callback_iface* m_cb = nullptr;
PaStream* m_stream = nullptr;
PaError m_err = paNoError;
mutable PaError m_err = paNoError;
public:
template<typename Callback, typename... Args>
stream(int in_c, int out_c, stream_fmt fmt, double samplerate, size_t buffersize, Callback&& cb, Args&&... cbargs):
stream(int in_c, int out_c, double samplerate, size_t buffersize, Callback&& cb, Args&&... cbargs):
m_cb(new detail::callback_impl<Callback,Args...>(std::forward<Callback>(cb), std::forward<Args>(cbargs)...)),
m_stream(initialize_global_instance()),
m_err(open_default_stream(&m_stream, in_c, out_c, fmt, samplerate, buffersize, m_cb)){}
m_err(open_default_stream(&m_stream, in_c, out_c, paFloat32, samplerate, buffersize, m_cb)){}
~stream();
int start();
int stop();
int abort();
int close();
error start();
error stop();
error abort();
error close();
bool is_stopped()const;
bool is_active()const;
int last_error()const;
error last_error()const;
private:
static PaStream* initialize_global_instance();
//to avoid using portaudio functions in the header
static int open_default_stream(PaStream**, int in_c, int out_c,
stream_fmt fmt, double samplerate,
int fmt, double samplerate,
size_t bufsize, detail::callback_iface* cb);
static int callback(const void* input, void* output, unsigned long framecount,
const PaStreamCallbackTimeInfo* /*timeInfo*/, PaStreamCallbackFlags /*statusFlags*/,

View File

@ -40,7 +40,7 @@ ifneq ($(WINDOWS),1)
CC::=gcc
CXX::=g++
LDLIBS::=
LDFLAGS::= -lglfw -lgl3w -ldl -lm -lportaudio -lasound
LDFLAGS::= -lglfw -lgl3w -ldl -lm -lportaudio -lasound -lsndfile
STRIP::=strip
RANLIB::=ranlib
AR::=ar

105
src/audio/error.cpp Normal file
View File

@ -0,0 +1,105 @@
/**
This file is a part of the WyZ project
Copyright (C) 2020 rexy712
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "audio/error.hpp"
#include <portaudio.h>
#include <sndfile.h>
namespace audio{
static int map_lib_errors(int liberr){
//portaudio and sndfile have no error overlaps, so a switch covers both
switch(liberr){
default:
case SF_ERR_NO_ERROR:
//case paNoError: //same value as SF_ERR_NO_ERROR
return error::NONE;
case paSampleFormatNotSupported:
case SF_ERR_UNRECOGNISED_FORMAT:
return error::UNSUPPORTED_FORMAT;
case SF_ERR_MALFORMED_FILE:
return error::BAD_FILE;
case SF_ERR_UNSUPPORTED_ENCODING:
return error::UNSUPPORTED_ENCODING;
case paInvalidChannelCount:
return error::INVALID_CHANNEL_COUNT;
case paInvalidSampleRate:
return error::INVALID_SAMPLE_RATE;
case paDeviceUnavailable:
case paInvalidDevice:
return error::INVALID_DEVICE;
case paInvalidFlag:
return error::INVALID_FLAG;
case paBadIODeviceCombination:
return error::BAD_DEVICE_COMBO;
case paInsufficientMemory:
return error::OUT_OF_MEMORY;
case paBufferTooBig:
return error::BUFFER_TOO_BIG;
case paBufferTooSmall:
return error::BUFFER_TOO_SMALL;
case paNullCallback:
return error::BAD_CALLBACK;
case paCanNotReadFromACallbackStream:
case paCanNotWriteToACallbackStream:
case paCanNotReadFromAnOutputOnlyStream:
case paCanNotWriteToAnInputOnlyStream:
case paBadStreamPtr:
return error::BAD_STREAM;
case paTimedOut:
return error::TIMED_OUT;
case paStreamIsStopped:
return error::STREAM_STOPPED;
case paStreamIsNotStopped:
return error::STREAM_NOT_STOPPED;
case paInputOverflowed:
return error::INPUT_OVERFLOW;
case paOutputUnderflowed:
return error::OUTPUT_UNDERFLOW;
case paBadBufferPtr:
return error::BAD_BUFFER;
case paIncompatibleStreamHostApi:
case paInvalidHostApi:
case paHostApiNotFound:
case paIncompatibleHostApiSpecificStreamInfo:
case paInternalError:
case paNotInitialized:
case paUnanticipatedHostError:
case SF_ERR_SYSTEM:
return error::SYSTEM;
}
}
error::error(int actual):
m_actual(actual),
m_homog(map_lib_errors(actual)){}
error::operator int()const{
return m_homog;
}
int error::get()const{
return m_homog;
}
int error::get_raw()const{
return m_actual;
}
}

View File

@ -41,4 +41,8 @@ namespace audio{
Pa_Terminate();
}
pa_system& pa_system::instance(){
static pa_system inst;
return inst;
}
}

119
src/audio/sndrd.cpp Normal file
View File

@ -0,0 +1,119 @@
/**
This file is a part of our_dick
Copyright (C) 2020 rexy712
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "audio/sndrd.hpp"
#include "audio/error.hpp"
#include <utility> //exchange, swap
namespace audio{
sndrd::sndrd(const char* f, mode m)noexcept:
m_info(),
m_fp(sf_open(f, static_cast<int>(m), &m_info)){}
sndrd::sndrd(sndrd&& f)noexcept:
m_info(f.m_info),
m_fp(std::exchange(f.m_fp, nullptr)){}
sndrd::~sndrd()noexcept{
close();
}
sndrd& sndrd::operator=(sndrd&& f)noexcept{
std::swap(m_fp, f.m_fp);
m_info = f.m_info;
return *this;
}
SNDFILE* sndrd::release()noexcept{
return std::exchange(m_fp, nullptr);
}
size_t sndrd::length()noexcept{
size_t pos = position();
size_t len = sf_seek(m_fp, 0, SEEK_END);
sf_seek(m_fp, pos, SEEK_SET);
return len;
}
size_t sndrd::position()const noexcept{
return sf_seek(m_fp, 0, SEEK_CUR);
}
void sndrd::set_pos(size_t newpos)noexcept{
sf_seek(m_fp, newpos, SEEK_SET);
}
void sndrd::seek(long int offset)noexcept{
size_t pos = position();
sf_seek(m_fp, pos + offset, SEEK_SET);
}
sndrd::operator SNDFILE*()noexcept{
return m_fp;
}
sndrd::operator const SNDFILE*()const noexcept{
return m_fp;
}
SNDFILE* sndrd::get()noexcept{
return m_fp;
}
const SNDFILE* sndrd::get()const noexcept{
return m_fp;
}
bool sndrd::valid()const noexcept{
return m_fp && !last_error();
}
size_t sndrd::read(float* dest, size_t items)noexcept{
return sf_read_float(m_fp, dest, items);
}
size_t sndrd::read_frames(float* dest, size_t frames)noexcept{
return sf_readf_float(m_fp, dest, frames);
}
size_t sndrd::write(const float* src, size_t items)noexcept{
return sf_write_float(m_fp, src, items);
}
size_t sndrd::write_frames(const float* src, size_t frames)noexcept{
return sf_writef_float(m_fp, src, frames);
}
void sndrd::close()noexcept{
if(m_fp)
sf_close(m_fp);
m_fp = nullptr;
}
size_t sndrd::frames()const noexcept{
return m_info.frames;
}
int sndrd::samplerate()const noexcept{
return m_info.samplerate;
}
int sndrd::channels()const noexcept{
return m_info.channels;
}
int sndrd::file_format()const noexcept{
return m_info.format;
}
int sndrd::sections()const noexcept{
return m_info.sections;
}
bool sndrd::seekable()const noexcept{
return m_info.seekable;
}
error sndrd::last_error()const noexcept{
return error(sf_error(m_fp));
}
}

View File

@ -18,6 +18,7 @@
#include "audio/stream.hpp"
#include "audio/init.hpp"
#include "audio/error.hpp"
#include <portaudio.h>
@ -27,45 +28,45 @@ namespace audio{
close();
delete m_cb;
}
int stream::start(){
return m_err = Pa_StartStream(m_stream);
error stream::start(){
return error(m_err = Pa_StartStream(m_stream));
}
int stream::stop(){
return m_err = Pa_StopStream(m_stream);
error stream::stop(){
return error(m_err = Pa_StopStream(m_stream));
}
int stream::abort(){
return m_err = Pa_AbortStream(m_stream);
error stream::abort(){
return error(m_err = Pa_AbortStream(m_stream));
}
bool stream::is_stopped()const{
return Pa_IsStreamStopped(m_stream);
}
bool stream::is_active()const{
return Pa_IsStreamActive(m_stream);
}
int stream::close(){
error stream::close(){
if(m_stream){
m_err = Pa_CloseStream(m_stream);
m_stream = nullptr;
return m_err;
return error(m_err);
}
return paNotInitialized;
return error(paNotInitialized);
}
int stream::last_error()const{
return m_err;
bool stream::is_stopped()const{
return (m_err = Pa_IsStreamStopped(m_stream)) == 1;
}
bool stream::is_active()const{
return (m_err = Pa_IsStreamActive(m_stream)) == 1;
}
error stream::last_error()const{
return error(m_err);
}
PaStream* stream::initialize_global_instance(){
static pa_system sys;
pa_system::instance();
return nullptr;
}
int stream::open_default_stream(PaStream** stream, int in_c, int out_c,
stream_fmt fmt, double samplerate,
int fmt, double samplerate,
size_t bufsize, detail::callback_iface* cb)
{
return Pa_OpenDefaultStream(stream, in_c, out_c, static_cast<int>(fmt), samplerate, bufsize, callback, cb);
return Pa_OpenDefaultStream(stream, in_c, out_c, fmt, samplerate, bufsize, callback, cb);
}
int stream::callback(const void* input, void* output, unsigned long framecount,
const PaStreamCallbackTimeInfo* /*timeInfo*/, PaStreamCallbackFlags /*statusFlags*/,