Post-export Analysis

This commit is contained in:
Robin Gareus 2016-02-10 03:01:05 +01:00
parent 883a6a3d4e
commit c1642fead8
7 changed files with 320 additions and 14 deletions

View File

@ -0,0 +1,60 @@
/*
* Copyright (C) 2016 Robin Gareus <robin@gareus.org>
*
* 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __ardour_export_analysis_h__
#define __ardour_export_analysis_h__
#include <map>
#include <cstring>
#include <boost/shared_ptr.hpp>
#include "ardour/types.h"
namespace ARDOUR {
struct ExportAnalysis {
public:
ExportAnalysis ()
: loudness (0)
, loudness_range (0)
, have_loudness (false)
{
memset (_peaks, 0, sizeof(_peaks));
memset (_spectrum, 0, sizeof(_spectrum));
}
ExportAnalysis (const ExportAnalysis& other)
: loudness (other.loudness)
, loudness_range (other.loudness_range)
, have_loudness (other.have_loudness)
{
memcpy (_peaks, other._peaks, sizeof(_peaks));
memcpy (_spectrum, other._spectrum, sizeof(_spectrum));
}
float loudness;
float loudness_range;
bool have_loudness;
PeakData _peaks[800];
float _spectrum[800][256];
};
typedef boost::shared_ptr<ExportAnalysis> ExportAnalysisPtr;
typedef std::map<std::string, ExportAnalysisPtr> AnalysisResults;
} // namespace ARDOUR
#endif

View File

@ -22,6 +22,7 @@
#define __ardour_export_graph_builder_h__
#include "ardour/export_handler.h"
#include "ardour/export_analysis.h"
#include "audiographer/utils/identity_vertex.h"
@ -32,6 +33,7 @@ namespace AudioGrapher {
class SampleRateConverter;
class PeakReader;
class Normalizer;
class Analyser;
template <typename T> class Chunker;
template <typename T> class SampleFormatConverter;
template <typename T> class Interleaver;
@ -55,7 +57,9 @@ class LIBARDOUR_API ExportGraphBuilder
typedef boost::shared_ptr<AudioGrapher::Sink<Sample> > FloatSinkPtr;
typedef boost::shared_ptr<AudioGrapher::IdentityVertex<Sample> > IdentityVertexPtr;
typedef boost::shared_ptr<AudioGrapher::Analyser> AnalysisPtr;
typedef std::map<ExportChannelPtr, IdentityVertexPtr> ChannelMap;
typedef std::map<std::string, AnalysisPtr> AnalysisMap;
public:
@ -71,9 +75,14 @@ class LIBARDOUR_API ExportGraphBuilder
void cleanup (bool remove_out_files = false);
void set_current_timespan (boost::shared_ptr<ExportTimespan> span);
void add_config (FileSpec const & config);
void get_analysis_results (AnalysisResults& results);
private:
void add_analyser (const std::string& fn, AnalysisPtr ap) {
analysis_map.insert (std::make_pair (fn, ap));
}
void add_split_config (FileSpec const & config);
class Encoder {
@ -125,6 +134,7 @@ class LIBARDOUR_API ExportGraphBuilder
boost::ptr_list<Encoder> children;
int data_width;
AnalysisPtr analyser;
// Only one of these should be available at a time
FloatConverterPtr float_converter;
IntConverterPtr int_converter;
@ -245,6 +255,8 @@ class LIBARDOUR_API ExportGraphBuilder
std::list<Normalizer *> normalizers;
AnalysisMap analysis_map;
Glib::ThreadPool thread_pool;
};

View File

@ -24,6 +24,7 @@
#include <stdint.h>
#include "ardour/libardour_visibility.h"
#include "ardour/export_analysis.h"
#include "ardour/types.h"
#include "pbd/signals.h"
@ -79,6 +80,8 @@ class LIBARDOUR_API ExportStatus {
volatile uint32_t total_normalize_cycles;
volatile uint32_t current_normalize_cycle;
AnalysisResults result_map;
private:
volatile bool _aborted;
volatile bool _errors;

View File

@ -28,6 +28,7 @@
#include "audiographer/general/chunker.h"
#include "audiographer/general/interleaver.h"
#include "audiographer/general/normalizer.h"
#include "audiographer/general/analyser.h"
#include "audiographer/general/peak_reader.h"
#include "audiographer/general/sample_format_converter.h"
#include "audiographer/general/sr_converter.h"
@ -110,6 +111,7 @@ ExportGraphBuilder::reset ()
channel_configs.clear ();
channels.clear ();
normalizers.clear ();
analysis_map.clear();
}
void
@ -173,6 +175,16 @@ ExportGraphBuilder::add_config (FileSpec const & config)
}
}
void
ExportGraphBuilder::get_analysis_results (AnalysisResults& results) {
for (AnalysisMap::iterator i = analysis_map.begin(); i != analysis_map.end(); ++i) {
ExportAnalysisPtr p = i->second->result ();
if (p) {
results.insert (std::make_pair (i->first, p));
}
}
}
void
ExportGraphBuilder::add_split_config (FileSpec const & config)
{
@ -287,39 +299,39 @@ ExportGraphBuilder::Encoder::copy_files (std::string orig_path)
/* SFC */
ExportGraphBuilder::SFC::SFC (ExportGraphBuilder &, FileSpec const & new_config, framecnt_t max_frames)
ExportGraphBuilder::SFC::SFC (ExportGraphBuilder &parent, FileSpec const & new_config, framecnt_t max_frames)
: data_width(0)
{
config = new_config;
data_width = sndfile_data_width (Encoder::get_real_format (config));
unsigned channels = new_config.channel_config->get_n_chans();
analyser.reset (new Analyser (config.format->sample_rate(), channels, max_frames,
(framecnt_t) ceil (parent.timespan->get_length () * config.format->sample_rate () / (double) parent.session.nominal_frame_rate ())));
parent.add_analyser (config.filename->get_path (config.format), analyser);
if (data_width == 8 || data_width == 16) {
short_converter = ShortConverterPtr (new SampleFormatConverter<short> (channels));
short_converter->init (max_frames, config.format->dither_type(), data_width);
add_child (config);
analyser->add_output (short_converter);
} else if (data_width == 24 || data_width == 32) {
int_converter = IntConverterPtr (new SampleFormatConverter<int> (channels));
int_converter->init (max_frames, config.format->dither_type(), data_width);
add_child (config);
analyser->add_output (int_converter);
} else {
int actual_data_width = 8 * sizeof(Sample);
float_converter = FloatConverterPtr (new SampleFormatConverter<Sample> (channels));
float_converter->init (max_frames, config.format->dither_type(), actual_data_width);
add_child (config);
analyser->add_output (float_converter);
}
}
ExportGraphBuilder::FloatSinkPtr
ExportGraphBuilder::SFC::sink ()
{
if (data_width == 8 || data_width == 16) {
return short_converter;
} else if (data_width == 24 || data_width == 32) {
return int_converter;
} else {
return float_converter;
}
return analyser;
}
void

View File

@ -295,11 +295,12 @@ ExportHandler::command_output(std::string output, size_t size)
void
ExportHandler::finish_timespan ()
{
graph_builder->get_analysis_results (export_status->result_map);
while (config_map.begin() != timespan_bounds.second) {
ExportFormatSpecPtr fmt = config_map.begin()->second.format;
std::string filename = config_map.begin()->second.filename->get_path(fmt);
if (fmt->with_cue()) {
export_cd_marker_file (current_timespan, fmt, filename, CDMarkerCUE);
}
@ -312,15 +313,17 @@ ExportHandler::finish_timespan ()
export_cd_marker_file (current_timespan, fmt, filename, MP4Chaps);
}
/* close file first, otherwise TagLib enounters an ERROR_SHARING_VIOLATION
* The process cannot access the file because it is being used.
* ditto for post-export and upload.
*/
graph_builder->reset ();
if (fmt->tag()) {
/* close file first, otherwise TagLib enounters an ERROR_SHARING_VIOLATION
* The process cannot access the file because it is being used.
*
* TODO: check Umlauts and encoding in filename.
/* TODO: check Umlauts and encoding in filename.
* TagLib eventually calls CreateFileA(),
*/
export_status->active_job = ExportStatus::Tagging;
graph_builder->reset ();
AudiofileTagger::tag_file(filename, *SessionMetadata::Metadata());
}

View File

@ -52,6 +52,7 @@ ExportStatus::init ()
total_normalize_cycles = 0;
current_normalize_cycle = 0;
result_map.clear();
}
void

View File

@ -0,0 +1,215 @@
/*
* Copyright (C) 2016 Robin Gareus <robin@gareus.org>
*
* 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef AUDIOGRAPHER_ANALYSER_H
#define AUDIOGRAPHER_ANALYSER_H
#include <fftw3.h>
#include <vamp-hostsdk/PluginLoader.h>
#include <vamp-sdk/Plugin.h>
#include "audiographer/visibility.h"
#include "audiographer/sink.h"
#include "audiographer/routines.h"
#include "audiographer/utils/listed_source.h"
#include "pbd/fastlog.h"
#include "ardour/export_analysis.h"
namespace AudioGrapher
{
class /*LIBAUDIOGRAPHER_API*/ Analyser : public ListedSource<float>, public Sink<float>
{
public:
Analyser (float sample_rate, unsigned int channels, framecnt_t bufsize, framecnt_t n_samples)
: _ebur128_plugin (0)
, _sample_rate (sample_rate)
, _channels (channels)
, _bufsize (bufsize / channels)
, _n_samples (n_samples)
, _pos (0)
{
assert (bufsize % channels == 0);
//printf("NEW ANALYSER %p r:%.1f c:%d f:%ld l%ld\n", this, sample_rate, channels, bufsize, n_samples);
if (channels > 0 && channels <= 2) {
using namespace Vamp::HostExt;
PluginLoader* loader (PluginLoader::getInstance());
_ebur128_plugin = loader->loadPlugin ("libardourvampplugins:ebur128", sample_rate, PluginLoader::ADAPT_ALL_SAFE);
assert (_ebur128_plugin);
_ebur128_plugin->reset ();
_ebur128_plugin->initialise (channels, _bufsize, _bufsize);
}
_bufs[0] = (float*) malloc (sizeof(float) * _bufsize);
_bufs[1] = (float*) malloc (sizeof(float) * _bufsize);
const size_t peaks = sizeof(_result._peaks) / sizeof (ARDOUR::PeakData::PeakDatum) / 2;
_spp = ceil ((_n_samples + 1.f) / (float) peaks);
_fft_data_size = _bufsize / 2;
_fft_freq_per_bin = sample_rate / _fft_data_size / 2.f;
_fft_data_in = (float *) fftwf_malloc (sizeof(float) * _bufsize);
_fft_data_out = (float *) fftwf_malloc (sizeof(float) * _bufsize);
_fft_power = (float *) malloc (sizeof(float) * _fft_data_size);
for (uint32_t i = 0; i < _fft_data_size; ++i) {
_fft_power[i] = 0;
}
for (uint32_t i = 0; i < _bufsize; ++i) {
_fft_data_out[i] = 0;
}
_fft_plan = fftwf_plan_r2r_1d (_bufsize, _fft_data_in, _fft_data_out, FFTW_R2HC, FFTW_MEASURE);
_hann_window = (float *) malloc(sizeof(float) * _bufsize);
double sum = 0.0;
for (uint32_t i = 0; i < _bufsize; ++i) {
_hann_window[i] = 0.5f - (0.5f * (float) cos (2.0f * M_PI * (float)i / (float)(_bufsize)));
sum += _hann_window[i];
}
const double isum = 2.0 / sum;
for (uint32_t i = 0; i < _bufsize; ++i) {
_hann_window[i] *= isum;
}
}
~Analyser ()
{
delete _ebur128_plugin;
free (_bufs[0]);
free (_bufs[1]);
fftwf_destroy_plan (_fft_plan);
fftwf_free (_fft_data_in);
fftwf_free (_fft_data_out);
free (_fft_power);
free (_hann_window);
}
void process (ProcessContext<float> const & c)
{
framecnt_t n_samples = c.frames() / c.channels();
assert (c.frames() % c.channels() == 0);
assert (n_samples <= _bufsize);
//printf("PROC %p @%ld F: %ld, S: %ld C:%d\n", this, _pos, c.frames(), n_samples, c.channels());
float const * d = c.data ();
framecnt_t s;
for (s = 0; s < n_samples; ++s) {
_fft_data_in[s] = 0;
const framecnt_t pk = (_pos + s) / _spp;
for (unsigned int c = 0; c < _channels; ++c) {
const float v = *d;
_bufs[c][s] = v;
if (_result._peaks[pk].min > v) { _result._peaks[pk].min = *d; }
if (_result._peaks[pk].max < v) { _result._peaks[pk].max = *d; }
_fft_data_in[s] += v * _hann_window[s] / (float) _channels;
++d;
}
}
for (; s < _bufsize; ++s) {
for (unsigned int c = 0; c < _channels; ++c) {
_bufs[c][s] = 0.f;
_fft_data_in[s] = 0;
}
}
if (_ebur128_plugin) {
_ebur128_plugin->process (_bufs, Vamp::RealTime::fromSeconds ((double) _pos / _sample_rate));
}
fftwf_execute (_fft_plan);
_fft_power[0] = _fft_data_out[0] * _fft_data_out[0];
#define FRe (_fft_data_out[i])
#define FIm (_fft_data_out[_bufsize - i])
for (uint32_t i = 1; i < _fft_data_size - 1; ++i) {
_fft_power[i] = (FRe * FRe) + (FIm * FIm);
}
#undef FRe
#undef FIm
// TODO handle case where _pos / _spp != (_pos + _bufsize) / _spp
// TODO: get geometry from ExportAnalysis
const framecnt_t x = _pos / _spp;
const float range = 80; // dB
const double ypb = 256.0 / _fft_data_size;
for (uint32_t i = 1; i < _fft_data_size - 1; ++i) {
const float level = fft_power_at_bin (i, i);
if (level < -range) continue;
const float pk = level > 0.0 ? 1.0 : (range + level) / range;
const uint32_t y = 256 - ceil (i * ypb); // log-y?
assert (x >= 0 && x < 800);
assert (y < 256);
if (_result._spectrum[x][y] < pk) { _result._spectrum[x][y] = pk; }
}
_pos += n_samples;
/* pass audio audio through */
ListedSource<float>::output(c);
}
ARDOUR::ExportAnalysisPtr result () {
//printf("PROCESSED %ld / %ld samples\n", _pos, _n_samples);
if (_pos == 0) {
return ARDOUR::ExportAnalysisPtr ();
}
if (_ebur128_plugin) {
Vamp::Plugin::FeatureSet features = _ebur128_plugin->getRemainingFeatures ();
if (!features.empty() && features.size() == 2) {
_result.loudness = features[0][0].values[0];
_result.loudness_range = features[1][0].values[0];
_result.have_loudness = true;
}
}
return ARDOUR::ExportAnalysisPtr (new ARDOUR::ExportAnalysis (_result));
}
using Sink<float>::process;
private:
ARDOUR::ExportAnalysis _result;
Vamp::Plugin* _ebur128_plugin;
float _sample_rate;
unsigned int _channels;
framecnt_t _bufsize;
framecnt_t _n_samples;
framecnt_t _pos;
framecnt_t _spp;
float* _bufs[2];
float* _hann_window;
uint32_t _fft_data_size;
double _fft_freq_per_bin;
float* _fft_data_in;
float* _fft_data_out;
float* _fft_power;
fftwf_plan _fft_plan;
inline float fft_power_at_bin (const uint32_t b, const float norm) const {
const float a = _fft_power[b] * norm;
return a > 1e-12 ? 10.0 * fast_log10(a) : -INFINITY;
}
};
} // namespace
#endif