Merge branch 'dev'

This commit is contained in:
2016-07-26 12:41:07 +01:00
15 changed files with 577 additions and 43 deletions
+1 -1
View File
@@ -1 +1 @@
-I ./include -std=c++11
-I ./include -I ./external/Catch/include/ -std=c++11
+78 -10
View File
@@ -3,28 +3,96 @@ set(CMAKE_CXX_STANDARD 11)
# Set the project name
project (concatenator)
set(Boost_USE_STATIC_LIBS OFF)
set(Boost_USE_MULTITHREADED ON)
set(Boost_USE_STATIC_RUNTIME OFF)
find_package(Boost 1.60.0 COMPONENTS program_options log log_setup thread date_time filesystem system)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/")
###############################################################################
### Boost Settings
find_package(Boost 1.60.0 COMPONENTS program_options log log_setup thread date_time filesystem system REQUIRED)
find_package(LibSndFile REQUIRED)
if (NOT FO_BOOST_STATIC_LINK)
add_definitions(-DBOOST_ALL_NO_LIB -DBOOST_ALL_DYN_LINK -DBOOST_LOG_DYN_LINK)
endif()
set(Boost_USE_STATIC_LIBS OFF)
set(Boost_USE_MULTITHREADED ON)
set(Boost_USE_STATIC_RUNTIME OFF)
###############################################################################
# Set build flags
set(CMAKE_CXX_FLAGS "-g -Wall")
# Set cmake to output executable to the bin directory
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
include_directories(include)
# Find all the source fies in the src directory
file(GLOB SOURCES "src/*.cpp")
# Build the executable from the source files found
if(Boost_FOUND)
set(CONCATENATOR_SOURCE_DIR ${PROJECT_SOURCE_DIR}/src)
set(CONCATENATOR_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include)
set(CONCATENATOR_TEST_DIR ${PROJECT_SOURCE_DIR}/test)
set(CONCATENATOR_SOURCE_FILES
${CONCATENATOR_SOURCE_DIR}/Concatenator.cpp
${CONCATENATOR_SOURCE_DIR}/AudioDatabase.cpp
${CONCATENATOR_SOURCE_DIR}/AudioFile.cpp
${CONCATENATOR_SOURCE_DIR}/Logger.cpp
${CONCATENATOR_SOURCE_DIR}/ArgumentParser.cpp
)
set(CONCATENATOR_HEADER_FILES
${CONCATENATOR_INCLUDE_DIR}/ArgumentParser.h
${CONCATENATOR_INCLUDE_DIR}/AudioDatabase.h
${CONCATENATOR_INCLUDE_DIR}/AudioFile.h
${CONCATENATOR_INCLUDE_DIR}/Logger.h
)
set(CONCATENATOR_TEST_SOURCES
${CONCATENATOR_TEST_DIR}/Concatenator_Test.cpp
${CONCATENATOR_TEST_DIR}/Basic_Tests.cpp
)
include_directories(${CONCATENATOR_INCLUDE_DIR})
include_directories(${Boost_INCLUDE_DIRS})
add_executable(concatenator ${SOURCES})
include_directories(${LIBSNDFILE_INCLUDE_DIR})
add_subdirectory(external)
add_executable(concatenator ${CONCATENATOR_SOURCE_FILES} ${CONCATENATOR_HEADER_FILES})
# Link to external libraries
target_link_libraries(concatenator ${Boost_LIBRARIES})
target_link_libraries(concatenator ${LIBSNDFILE_LIBRARIES})
# Test build options (this code adapted from: https://github.com/ComicSansMS/GhulbusBase/blob/master/CMakeLists.txt)
option(BUILD_TESTS "Determines whether to build tests." ON)
if(BUILD_TESTS)
enable_testing()
if(NOT TARGET Catch)
include(ExternalProject)
if(WIN32)
set(FETCH_EXTERNAL_CATCH
URL https://github.com/philsquared/Catch/archive/v1.2.1-develop.12.zip
URL_HASH MD5=cda228922a1c9248364c99a3ff9cd9fa)
else()
set(FETCH_EXTERNAL_CATCH
URL https://github.com/philsquared/Catch/archive/v1.2.1-develop.12.tar.gz
URL_HASH MD5=a8dfb7be899a6e7fb30bd55d53426122)
endif()
ExternalProject_Add(Catch-External
PREFIX ${CMAKE_BINARY_DIR}/external/Catch
${FETCH_EXTERNAL_CATCH}
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/external/Catch/src/Catch-External/single_include/catch.hpp
${CMAKE_BINARY_DIR}/external/Catch/include/catch.hpp
)
add_library(Catch INTERFACE)
add_dependencies(Catch Catch-External)
target_include_directories(Catch INTERFACE ${CMAKE_BINARY_DIR}/external/Catch/include)
endif()
add_executable(Concatenator_Test ${CONCATENATOR_TEST_SOURCES})
target_link_libraries(Concatenator_Test Catch)
add_test(NAME TestBase COMMAND Concatenator_Test)
endif()
+34
View File
@@ -0,0 +1,34 @@
# - Try to find libsndfile
# Once done, this will define
#
# LIBSNDFILE_FOUND - system has libsndfile
# LIBSNDFILE_INCLUDE_DIRS - the libsndfile include directories
# LIBSNDFILE_LIBRARIES - link these to use libsndfile
# Use pkg-config to get hints about paths
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
pkg_check_modules(LIBSNDFILE_PKGCONF sndfile)
endif(PKG_CONFIG_FOUND)
# Include dir
find_path(LIBSNDFILE_INCLUDE_DIR
NAMES sndfile.h
PATHS ${LIBSNDFILE_PKGCONF_INCLUDE_DIRS}
)
# Library
find_library(LIBSNDFILE_LIBRARY
NAMES sndfile libsndfile-1
PATHS ${LIBSNDFILE_PKGCONF_LIBRARY_DIRS}
)
find_package(PackageHandleStandardArgs)
find_package_handle_standard_args(LibSndFile DEFAULT_MSG LIBSNDFILE_LIBRARY LIBSNDFILE_INCLUDE_DIR)
if(LIBSNDFILE_FOUND)
set(LIBSNDFILE_LIBRARIES ${LIBSNDFILE_LIBRARY})
set(LIBSNDFILE_INCLUDE_DIRS ${LIBSNDFILE_INCLUDE_DIR})
endif(LIBSNDFILE_FOUND)
mark_as_advanced(LIBSNDFILE_LIBRARY LIBSNDFILE_LIBRARIES LIBSNDFILE_INCLUDE_DIR LIBSNDFILE_INCLUDE_DIRS)
View File
+22 -3
View File
@@ -1,7 +1,13 @@
#include <iostream>
#include "boost/program_options.hpp"
#include <string>
#include <vector>
#include <boost/program_options.hpp>
#include <boost/filesystem.hpp>
using std::vector;
using std::string;
namespace po = boost::program_options;
namespace fs = boost::filesystem;
class ArgumentParser {
public:
@@ -10,12 +16,25 @@ class ArgumentParser {
ArgumentParser(const ArgumentParser&);
ArgumentParser& operator=(const ArgumentParser&);
int parseargs(int argc, char** argv);
po::variables_map::size_type count(char const *ref);
const po::variable_value& operator [](char const *b) const;
//std::string& operator [](char const *b);
private:
// Stores values for arguments parsed from the command line
po::variables_map vm;
//Create a positional options object for parsing input, output etc
//positional arguments from command line.
po::positional_options_description positionalOptions;
//
po::variables_map vm;
po::options_description desc;
};
class ConcatenatorArgParse : public ArgumentParser {
public:
vector<string> get_analyses() { return (*this)["analyses"].as<vector<string>>(); }
string get_source_db() { return (*this)["source"].as<string>(); }
string get_target_db() { return (*this)["target"].as<string>(); }
fs::path get_tar_audio_dir() { return ((*this)["tar_audio"].empty() ? fs::path("") : fs::path((*this)["tar_audio"].as<string>())); }
fs::path get_src_audio_dir() { return ((*this)["src_audio"].empty() ? fs::path("") : fs::path((*this)["src_audio"].as<string>())); }
};
+88
View File
@@ -0,0 +1,88 @@
#include <iostream>
#include <string>
#include <list>
#include <set>
#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
#include "logger.h"
using std::string;
using std::cout;
using std::endl;
using std::list;
using std::vector;
/*!
* A class that encapsulates a collection of AudioFile objects in order to
* perform analysis and synthesis operations on batches of audio files.
*/
class AudioDatabase {
public:
AudioDatabase(
const std::string database_dir,
vector<string>& analyses,
Logger* log
);
void load_database(boost::filesystem::path source_dir, bool reanalyse=false);
private:
boost::filesystem::path database_dir;
boost::filesystem::path audio_dir;
// Define a set that stores the locations of audiofiles in the database.
std::set<boost::filesystem::path> audio_file_set;
std::map<string, boost::filesystem::path> database_dirs;
Logger* log;
void validate_analysis_list();
bool validate_filetype(const boost::filesystem::path& filepath);
void create_subdirs();
void organise_audio(boost::filesystem::path source_dir, bool symlink=true);
void register_audio();
};
/*! A function that determines whether a string value is found in the container.
*/
template<typename container>
bool in_array(string &value, const container &array)
{
boost::algorithm::to_upper(value);
return std::find(array.begin(), array.end(), value) != array.end();
}
/*! Check that analysis strings provided are supported by the database object
\param iterator - An iterator pointing to where to begin checking strings are valid.
\param end - An iterator pointing to the point at which to stop analysing strings.
*/
template<typename Iter>
std::list<string> check_analyses_valid(Iter iterator, Iter end)
{
static std::list<string> valid_analyses = {
"RMS",
"ZEROX",
"FFT",
"SPCCNTR",
"SPCSPRD",
"SPCFLUX",
"SPCCF",
"SPCFLATNESS",
"F0",
"PEAK",
"CENTROID",
"VARIANCE",
"KURTOSIS",
"SKEWNESS",
"HARM_RATIO"
};
std::list<string> invalid;
while(iterator != end)
{
if(!in_array(*iterator, valid_analyses)) {
invalid.push_back(*iterator);
}
++iterator;
}
return invalid;
}
+19
View File
@@ -0,0 +1,19 @@
#include <string>
#include <sndfile.hh>
class AudioFile {
public:
AudioFile(const char * &name, const int &mode=SFM_RDWR, const int &format=0, const int &channels=0, const int &samplerate=0);
int open(const int &mode=SFM_READ, const int &format=0, const int &channels=0, const int &samplerate=0);
protected:
SndfileHandle file;
SF_INFO* file_info;
private:
std::string name;
};
class AnalysedAudioFile : public AudioFile {
public:
private:
};
+6 -3
View File
@@ -1,3 +1,5 @@
#ifndef LOGGER_H
#define LOGGER_H
#include <boost/shared_ptr.hpp>
#include <boost/log/sinks.hpp>
#include <boost/log/sources/severity_logger.hpp>
@@ -8,7 +10,7 @@
class Logger {
public:
Logger();
~Logger() {};
~Logger();
void trace(std::string str);
void debug(std::string str);
void info(std::string str);
@@ -21,8 +23,8 @@ class Logger {
static void log_formatter(boost::log::record_view const& rec, boost::log::formatting_ostream& strm);
// Define types for logging backends
typedef boost::log::sinks::asynchronous_sink<boost::log::sinks::text_ostream_backend> console_backend;
typedef boost::log::sinks::asynchronous_sink<boost::log::sinks::text_file_backend> file_backend;
typedef boost::log::sinks::synchronous_sink<boost::log::sinks::text_ostream_backend> console_backend;
typedef boost::log::sinks::synchronous_sink<boost::log::sinks::text_file_backend> file_backend;
// Define a sink for console output and for log file output
boost::shared_ptr<console_backend> console_sink;
@@ -31,3 +33,4 @@ class Logger {
//Define a logger
boost::log::sources::severity_logger< boost::log::trivial::severity_level > lg;
};
#endif
+29 -5
View File
@@ -1,29 +1,53 @@
#include "ArgumentParser.h"
#include <vector>
#include <iostream>
using namespace std;
ArgumentParser::ArgumentParser() : desc("Allowed options") {
// Add positional arguments to specify source, target and output database locations.
positionalOptions.add("source_db", 1);
positionalOptions.add("target_db", 1);
positionalOptions.add("output_db", 1);
positionalOptions.add("source", 1);
positionalOptions.add("target", 1);
positionalOptions.add("output", 1);
// Add optional arguments to allow control over application settings from the command line.
desc.add_options()
("help,h", "produce help message")
("compression", po::value<int>(), "set compression level")
("source", po::value<string>()->required(), "Source location")
("target", po::value<string>()->required(), "Target location")
("output", po::value<string>()->required(), "Output location")
("analyses,a", po::value<vector<string>>()->multitoken(), "Analysis "
"strings specifying analyses to use for database comparison.")
("src_audio", po::value<string>(), "Specifies the "
"directory to create the source database and store analyses in. If "
"not specified then the " "source directory will be used directly.")
("tar_audio", po::value<string>(), "Specifies the "
"directory to create the target database and store analyses in. If "
"not specified then the target directory will be used directly.")
;
}
int ArgumentParser::parseargs(int argc, char** argv) {
po::store(po::command_line_parser(argc, argv).options(desc).positional(positionalOptions).run(), vm);
po::notify(vm);
// If help option is specified then output help message
if (vm.count("help")) {
cout << desc << "\n";
return 1;
}
po::notify(vm);
if (vm["analyses"].empty()) {
throw runtime_error("No analysis strings provided as arguments.");
}
return 0;
}
const po::variable_value& ArgumentParser::operator [](char const *b) const {
return vm[b];
}
po::variables_map::size_type ArgumentParser::count(char const *ref) {
return vm.count(ref);
}
+202
View File
@@ -0,0 +1,202 @@
#include <string>
#include <vector>
#include <list>
#include "AudioDatabase.h"
#include <stdexcept>
#include <set>
#include <algorithm>
#include <boost/algorithm/string/join.hpp>
#include <boost/filesystem.hpp>
#include <boost/regex.hpp>
namespace fs = boost::filesystem;
using namespace std;
AudioDatabase::AudioDatabase(
const string database_dir,
vector<string>& analyses,
Logger* log
)
{
this->log = log;
log->info("Database directory: " + database_dir);
// Remove duplicate strings from vector of analyses.
std::vector<string>::iterator it;
it = std::unique (analyses.begin(), analyses.end());
analyses.resize(std::distance(analyses.begin(),it));
// Check that all analysis strings supplied refer to valid analyses.
list<string> invalid = check_analyses_valid(analyses.begin(), analyses.end());
if(!invalid.empty()) {
string err = "The following analysis string(s) supplied to the AudioDatabase constructor are not valid: ";
string invalid_strings = boost::algorithm::join(invalid, " ");
throw std::runtime_error(err + invalid_strings);
}
database_dirs.insert({"root", fs::path(database_dir)});
this->audio_dir = fs::path(audio_dir);
}
void AudioDatabase::validate_analysis_list()
{
}
void AudioDatabase::load_database(fs::path source_dir, bool reanalyse)
{
// Make sure the database root directory exists.
try
{
if(create_directory(database_dirs["root"]))
{
log->debug("Database directory created: " + database_dir.string());
}
else if(exists(database_dirs["root"]))
{
log->debug("Database directory already exists: " + database_dir.string());
}
}
catch(boost::filesystem::filesystem_error &e)
{
throw std::runtime_error("Database directory could not be created: " + database_dir.string());
}
// Create a folder hierachy used to store audio and analysis data used by the database.
create_subdirs();
if(source_dir.empty()) {
source_dir = database_dirs["audio"];
log->debug("Source directory not provided. Setting to:" + source_dir.string());
}
if(!exists(source_dir)) {
throw std::runtime_error("Source audio directory does not exist: " + source_dir.string());
}
// Only organise audio if new audio is to be added from a new location.
if(source_dir != database_dirs["audio"]) {
// Copy/create links to audio files that are to be used as part of the database.
organise_audio(source_dir);
}
// Find all audio in the database and store references for use later...
register_audio();
}
void AudioDatabase::create_subdirs()
{
array<fs::path, 2> directory_names = {{
fs::path("audio"),
fs::path("data")
}};
for(const auto& name : directory_names) {
fs::path subdir = database_dirs["root"]/name;
try
{
if(create_directory(subdir)) {
log->info("Subdirectory created: " + subdir.string());
}
else if(exists(database_dirs["root"]))
{
log->info("Subdirectory already exists: " + subdir.string());
}
}
catch(boost::filesystem::filesystem_error &e)
{
throw std::runtime_error("Subdirectory could not be created: " + database_dirs["root"].string());
}
database_dirs.insert({name.string(), subdir});
}
}
bool AudioDatabase::validate_filetype(const fs::path& filepath)
{
// Define patterns to validate files found based on their file extensions.
static array<string, 4> valid_filetypes = {{
".wav",
".aif",
".aiff",
".flac"
}};
for(const auto& filetype : valid_filetypes)
{
// compare file extension with valid extension strings to find match.
bool valid = (filetype.compare(filepath.extension().string()) == 0);
if(valid) {
return true;
}
}
return false;
}
void AudioDatabase::organise_audio(fs::path source_dir, bool symlink)
{
log->info("Organising audio directory at: " + database_dirs["audio"].string());
// Define the destination for copying/linking all valid audio files found.
for(fs::recursive_directory_iterator iter(source_dir), end; iter != end; ++iter)
{
// Don't search the audio directory of the database if this is a subdirectory of the source directory provided.
if (iter->path() == database_dirs["audio"])
{
iter.disable_recursion_pending();
}
if(!validate_filetype(iter->path())) {
log->debug("File: " + iter->path().string() + " isn't a supported audiofile. Skipping...");
continue;
}
fs::path destination_file = database_dirs["audio"]/iter->path().filename();
if(symlink) {
// Try to symlink the file to the audio directory of the database.
try {
fs::create_symlink(iter->path(), destination_file);
log->debug("Linked: " + iter->path().string() + " to: " + destination_file.string());
}
catch(boost::filesystem::filesystem_error &e){
// If symbolic linking fails then the file probably already exists at the location.
log->debug("Failed to link: " + iter->path().string() + " to " + destination_file.string() + " File may already exists.");
}
}
else {
// If it is in the database as a symlink, but a full copy is required
if(fs::exists(destination_file) && !fs::is_symlink(destination_file)) {
log->debug("File already exists: " + iter->path().string());
continue;
}
// Copy file, overwriting any previously created symbolic links.
try {
fs::remove(destination_file);
fs::copy_file(iter->path(), destination_file, fs::copy_option::overwrite_if_exists);
log->debug("Copied: " + iter->path().string() + " to: " + destination_file.string());
}
catch(boost::filesystem::filesystem_error &e){
// If symbolic linking fails then the file probably already exists at the location.
log->debug("Failed to copy source file to: " + destination_file.string() + " File may already exists.");
}
}
}
}
void AudioDatabase::register_audio()
{
// Clear any previous entries from set.
audio_file_set.clear();
for(auto& entry : boost::make_iterator_range(fs::directory_iterator(database_dirs["audio"]), {}))
{
if(validate_filetype(entry.path())) {
log->info("Registered audio file: " + entry.path().string());
audio_file_set.insert(entry.path());
}
}
}
+21 -10
View File
@@ -1,12 +1,23 @@
#include <sndfile.hh>
#include "AudioFile.h"
#include <string>
#include <vector>
#include <iostream>
using namespace std;
class AudioFile {
public:
private:
SndfileHandle file;
};
AudioFile::AudioFile(const char * &name, const int &mode, const int &format, const int &channels, const int &samplerate)
{
this->name = name;
open(mode, format, channels, samplerate);
}
int AudioFile::open(const int &mode, const int &format, const int &channels, const int &samplerate)
{
switch(mode){
case SFM_READ: file = SndfileHandle(name);
case SFM_WRITE: file = SndfileHandle(name, SFM_WRITE, format, channels, samplerate);
case SFM_RDWR: file = SndfileHandle(name, SFM_RDWR, format, channels, samplerate);
}
return 0;
}
class AnalysedAudioFile : public AudioFile {
public:
private:
};
+60 -7
View File
@@ -1,15 +1,68 @@
#include <iostream>
#include "Logger.h"
#include "logger.h"
#include "ArgumentParser.h"
#include "AudioDatabase.h"
#include <list>
#include <string>
using namespace std;
int main(int argc, char** argv) {
Logger log = Logger();
namespace
{
const size_t ERROR_IN_COMMAND_LINE = 1;
const size_t SUCCESS = 0;
const size_t ERROR_UNHANDLED_EXCEPTION = 2;
ArgumentParser argparse = ArgumentParser();
argparse.parseargs(argc, argv);
log.error("My pretty little error!");
return 0;
}
int main(int argc, char** argv) {
// Initialize a logger object to be used for message handeling throughout
// the program
Logger log = Logger();
/*
try
{
*/
// Initialize object to parse arguments supplied by user from command
// line
ConcatenatorArgParse argparse = ConcatenatorArgParse();
// Parse arguments and exit program if specified (through use of --help
// or -h flag)
if(argparse.parseargs(argc, argv)) {
return SUCCESS;
}
vector<string> analyses = argparse.get_analyses();
// Initialize the source audio database object with arguments provided from the command line.
AudioDatabase source_db = AudioDatabase(
argparse.get_source_db(),
analyses,
&log
);
source_db.load_database(argparse.get_src_audio_dir());
/*
// Initialize the target audio database object with arguments provided from the command line.
AudioDatabase target_db = AudioDatabase(
argparse.get_target_db(),
analyses,
&log
);
target_db.load_database(argparse.get_tar_audio_dir());
*/
/*
}
catch(std::exception& e)
{
string error("Unhandled Exception reached the top of main:\n");
error.append(e.what());
log.error(error);
throw;
}
*/
return SUCCESS;
}
+8
View File
@@ -69,6 +69,10 @@ Logger::Logger() {
};
Logger::~Logger() {
Logger::file_sink->flush();
Logger::console_sink->flush();
}
void Logger::trace(std::string str) {
BOOST_LOG_SEV(lg, logging::trivial::trace) << str;
}
@@ -87,8 +91,12 @@ void Logger::warning(std::string str) {
void Logger::error(std::string str) {
BOOST_LOG_SEV(lg, logging::trivial::error) << str;
Logger::file_sink->flush();
Logger::console_sink->flush();
}
void Logger::fatal(std::string str) {
BOOST_LOG_SEV(lg, logging::trivial::fatal) << str;
Logger::file_sink->flush();
Logger::console_sink->flush();
}
+1
View File
@@ -0,0 +1 @@
#include "catch.hpp"
+4
View File
@@ -0,0 +1,4 @@
#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
#include <catch.hpp>