annotate src/file_comparer.cpp @ 0:add35537fdbb tip

Initial import
author irh <ian.r.hobson@gmail.com>
date Thu, 25 Aug 2011 11:05:55 +0100
parents
children
rev   line source
ian@0 1 // Copyright 2011, Ian Hobson.
ian@0 2 //
ian@0 3 // This file is part of gpsynth.
ian@0 4 //
ian@0 5 // gpsynth is free software: you can redistribute it and/or modify
ian@0 6 // it under the terms of the GNU General Public License as published by
ian@0 7 // the Free Software Foundation, either version 3 of the License, or
ian@0 8 // (at your option) any later version.
ian@0 9 //
ian@0 10 // gpsynth is distributed in the hope that it will be useful,
ian@0 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
ian@0 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
ian@0 13 // GNU General Public License for more details.
ian@0 14 //
ian@0 15 // You should have received a copy of the GNU General Public License
ian@0 16 // along with gpsynth in the file COPYING.
ian@0 17 // If not, see http://www.gnu.org/licenses/.
ian@0 18
ian@0 19 #include "file_comparer.hpp"
ian@0 20
ian@0 21 #include "boost_ex.hpp"
ian@0 22 #include "statistics.hpp"
ian@0 23 #include "std_ex.hpp"
ian@0 24
ian@0 25 #include "boost/bind.hpp"
ian@0 26 #include "boost/algorithm/string/classification.hpp"
ian@0 27 #include "boost/algorithm/string/split.hpp"
ian@0 28 #include "boost/math/special_functions/fpclassify.hpp"
ian@0 29
ian@0 30 #include <algorithm>
ian@0 31 #include <cmath>
ian@0 32 #include <cstddef>
ian@0 33 #include <iostream>
ian@0 34 #include <iterator>
ian@0 35
ian@0 36 // maps the frequency range 20-20480Hz to the 0-1 range on a log scale
ian@0 37 // with 0.1 = 1 octave
ian@0 38 namespace {
ian@0 39
ian@0 40 dsp::Value ConvertFrequency(dsp::Value frequency) {
ian@0 41 frequency = stdx::Clamp(frequency, 20.0, 20480.0);
ian@0 42 return (::log2(frequency / 440.0) * 10 + 44.5943) / 100.0;
ian@0 43 }
ian@0 44
ian@0 45 void ConvertFrequencyList(dsp::ValueList& frequencies) {
ian@0 46 std::transform(frequencies.begin(), frequencies.end(), frequencies.begin(),
ian@0 47 &ConvertFrequency);
ian@0 48 }
ian@0 49
ian@0 50 } // namespace
ian@0 51
ian@0 52 namespace dsp {
ian@0 53
ian@0 54 // The FeatureComparer class abstracts common code required by feature set
ian@0 55 // comparisons.
ian@0 56 template<typename T>
ian@0 57 class FeatureComparer : public FeatureComparerInterface {
ian@0 58 public:
ian@0 59 typedef void (*ConversionFunction)(T&);
ian@0 60 typedef const T& (FeatureExtractor::*FeatureFunction)();
ian@0 61 typedef typename T::value_type ValueT;
ian@0 62 typedef typename T::const_iterator IteratorT;
ian@0 63
ian@0 64 private:
ian@0 65 //
ian@0 66 FeatureExtractor* extractor_;
ian@0 67 // A member function of FeatureExtractor that retrieves an audio feature
ian@0 68 FeatureFunction feature_function_;
ian@0 69 // Stores the target feature for comparison
ian@0 70 T target_buffer_;
ian@0 71 // Temporary use stored as member to prevent reallocations
ian@0 72 T comparison_buffer_;
ian@0 73 // Used if a feature data set needs to have a conversion applied to it
ian@0 74 ConversionFunction converter_;
ian@0 75 // the feature's ID
ian@0 76 int id_;
ian@0 77 // true if the feature should be scaled by the frame's energy
ian@0 78 bool scale_by_energy_;
ian@0 79
ian@0 80 public:
ian@0 81 FeatureComparer(int id,
ian@0 82 FeatureExtractor* extractor,
ian@0 83 FeatureFunction feature_function,
ian@0 84 ConversionFunction converter = NULL,
ian@0 85 bool scale_by_energy = false)
ian@0 86 : id_(id),
ian@0 87 extractor_(extractor),
ian@0 88 feature_function_(feature_function),
ian@0 89 converter_(converter),
ian@0 90 scale_by_energy_(scale_by_energy)
ian@0 91 {}
ian@0 92
ian@0 93 // copy constructor
ian@0 94 FeatureComparer(const FeatureComparer& other)
ian@0 95 : id_(other.id_),
ian@0 96 extractor_(other.extractor_),
ian@0 97 feature_function_(other.feature_function_),
ian@0 98 converter_(other.converter_),
ian@0 99 scale_by_energy_(other.scale_by_energy_),
ian@0 100 target_buffer_(other.target_buffer_) // copy the target value buffer
ian@0 101 // no need to copy comparison buffer
ian@0 102 {}
ian@0 103
ian@0 104 virtual void AnalyzeTarget() {
ian@0 105 // retrieve the target feature
ian@0 106 target_buffer_ = (extractor_->*feature_function_)();
ian@0 107 if (converter_ != NULL) {
ian@0 108 converter_(target_buffer_);
ian@0 109 }
ian@0 110 if (scale_by_energy_) {
ian@0 111 std::transform(target_buffer_.begin(), target_buffer_.end(),
ian@0 112 extractor_->Energy().begin(),
ian@0 113 target_buffer_.begin(),
ian@0 114 std::multiplies<ValueT>());
ian@0 115 }
ian@0 116 }
ian@0 117
ian@0 118 virtual Value Compare() {
ian@0 119 // get the values to compare
ian@0 120 const ValueList& feature = (extractor_->*feature_function_)();
ian@0 121 ValueList* feature_buffer;
ian@0 122 if (converter_ != NULL || scale_by_energy_) {
ian@0 123 comparison_buffer_ = feature;
ian@0 124 if (converter_ != NULL) {
ian@0 125 converter_(comparison_buffer_);
ian@0 126 }
ian@0 127 if (scale_by_energy_) {
ian@0 128 std::transform(comparison_buffer_.begin(), comparison_buffer_.end(),
ian@0 129 extractor_->Energy().begin(),
ian@0 130 comparison_buffer_.begin(),
ian@0 131 std::multiplies<ValueT>());
ian@0 132 }
ian@0 133 feature_buffer = &comparison_buffer_;
ian@0 134 } else {
ian@0 135 feature_buffer = const_cast<ValueList*>(&feature);
ian@0 136 }
ian@0 137 // call conversion function
ian@0 138
ian@0 139 // find range of buffer to compare against target
ian@0 140 IteratorT buffer_start = feature_buffer->begin();
ian@0 141 IteratorT buffer_end;
ian@0 142 if (feature_buffer->size() > target_buffer_.size()) {
ian@0 143 buffer_end = buffer_start + target_buffer_.size();
ian@0 144 } else {
ian@0 145 buffer_end = feature_buffer->end();
ian@0 146 }
ian@0 147 // return root mean square error of buffer differences
ian@0 148 return std::sqrt(stats::MeanSquaredError(buffer_start,
ian@0 149 buffer_end,
ian@0 150 target_buffer_.begin()));
ian@0 151 }
ian@0 152
ian@0 153 virtual FeatureComparerInterface* Clone() const {
ian@0 154 return new FeatureComparer<T>(*this);
ian@0 155 }
ian@0 156
ian@0 157 virtual void SetExtractor(FeatureExtractor* extractor) {
ian@0 158 extractor_ = extractor;
ian@0 159 }
ian@0 160
ian@0 161 virtual int ID() const { return id_; }
ian@0 162 };
ian@0 163
ian@0 164 // specializations for std::vector<ValueList>
ian@0 165 template<>
ian@0 166 void FeatureComparer<std::vector<ValueList> >::AnalyzeTarget() {
ian@0 167 target_buffer_ = (extractor_->*feature_function_)();
ian@0 168 if (converter_ != NULL) {
ian@0 169 converter_(target_buffer_);
ian@0 170 }
ian@0 171 // for vector features we can scale by energy on a frame by frame basis
ian@0 172 }
ian@0 173
ian@0 174 template<>
ian@0 175 Value FeatureComparer<std::vector<ValueList> >::Compare() {
ian@0 176 // get the values to compare
ian@0 177 const std::vector<ValueList>& feature = (extractor_->*feature_function_)();
ian@0 178 std::vector<ValueList>* feature_buffer;
ian@0 179 // call conversion function
ian@0 180 if (converter_ != NULL) {
ian@0 181 comparison_buffer_ = feature;
ian@0 182 converter_(comparison_buffer_);
ian@0 183 feature_buffer = &comparison_buffer_;
ian@0 184 } else {
ian@0 185 // this isn't pretty, but it's the only way to allow avoiding a copy
ian@0 186 // when no conversion is taking place..
ian@0 187 feature_buffer = const_cast<std::vector<ValueList>*>(&feature);
ian@0 188 }
ian@0 189 // find how many frames to compare
ian@0 190 std::size_t frames_to_compare;
ian@0 191 if (feature_buffer->size() > target_buffer_.size()) {
ian@0 192 frames_to_compare = target_buffer_.size();
ian@0 193 } else {
ian@0 194 frames_to_compare = feature_buffer->size();
ian@0 195 }
ian@0 196 // take average of RMSE over all frames
ian@0 197 Value error = 0;
ian@0 198 const ValueList& energy = extractor_->Energy();
ian@0 199 if (scale_by_energy_) {
ian@0 200 for (std::size_t i = 0; i < frames_to_compare; ++i) {
ian@0 201 Value frame_error;
ian@0 202 frame_error = std::sqrt(stats::MeanSquaredError(feature_buffer->at(i),
ian@0 203 target_buffer_[i]));
ian@0 204 error += frame_error * energy[i];
ian@0 205 }
ian@0 206 } else {
ian@0 207 for (std::size_t i = 0; i < frames_to_compare; ++i) {
ian@0 208 error += std::sqrt(stats::MeanSquaredError(feature_buffer->at(i),
ian@0 209 target_buffer_[i]));
ian@0 210 }
ian@0 211 }
ian@0 212 return error / frames_to_compare;
ian@0 213 }
ian@0 214
ian@0 215
ian@0 216 FileComparer::FileComparer(int window_size /* = 1024 */,
ian@0 217 int hop_size /* = 256 */)
ian@0 218 : extractor_("", window_size, hop_size),
ian@0 219 target_duration_(0)
ian@0 220 {
ian@0 221 EnableFeature(Feature::LogMagnitude);
ian@0 222 EnableFeature(Feature::Pitch);
ian@0 223 }
ian@0 224
ian@0 225 FileComparer::FileComparer(const std::string& feature_list,
ian@0 226 int window_size /* = 1024 */,
ian@0 227 int hop_size /* = 256 */)
ian@0 228 : extractor_("", window_size, hop_size),
ian@0 229 target_duration_(0)
ian@0 230 {
ian@0 231 feature_names_["pitch"] = Feature::Pitch;
ian@0 232 feature_names_["energy"] = Feature::Energy;
ian@0 233 feature_names_["mfccs"] = Feature::MFCCs;
ian@0 234 feature_names_["dmfccs"] = Feature::DeltaMFCCs;
ian@0 235 feature_names_["ddmfccs"] = Feature::DoubleDeltaMFCCs;
ian@0 236 feature_names_["mag"] = Feature::Magnitude;
ian@0 237 feature_names_["logmag"] = Feature::LogMagnitude;
ian@0 238 feature_names_["centroid"] = Feature::SpectralCentroid;
ian@0 239 feature_names_["spread"] = Feature::SpectralSpread;
ian@0 240 feature_names_["flux"] = Feature::SpectralFlux;
ian@0 241 EnableFeatures(feature_list);
ian@0 242 }
ian@0 243
ian@0 244 FileComparer::FileComparer(const FileComparer& other)
ian@0 245 : target_file_(other.target_file_),
ian@0 246 target_duration_(other.target_duration_),
ian@0 247 extractor_("",
ian@0 248 other.extractor_.WindowSize(),
ian@0 249 other.extractor_.HopSize())
ian@0 250 {
ian@0 251 foreach (const FeatureComparerPtr& pointer, other.features_) {
ian@0 252 // clone the feature comparer
ian@0 253 features_.push_back(FeatureComparerPtr(pointer->Clone()));
ian@0 254 features_.back()->SetExtractor(&extractor_);
ian@0 255 }
ian@0 256 }
ian@0 257
ian@0 258 void FileComparer::SetFeatureExtractorSettings(int window_size, int hop_size) {
ian@0 259 bool reload_target = false;
ian@0 260 if (extractor_.WindowSize() != window_size) {
ian@0 261 extractor_.SetWindowSize(window_size);
ian@0 262 reload_target = true;
ian@0 263 }
ian@0 264 if (extractor_.HopSize() != hop_size) {
ian@0 265 extractor_.SetHopSize(hop_size);
ian@0 266 reload_target = true;
ian@0 267 }
ian@0 268 if (reload_target && !target_file_.empty()) {
ian@0 269 SetTargetFile(target_file_);
ian@0 270 }
ian@0 271 }
ian@0 272
ian@0 273 void FileComparer::SetTargetFile(const std::string& target_file) {
ian@0 274 if (target_file_ != target_file) {
ian@0 275 target_file_ = target_file;
ian@0 276 extractor_.LoadFile(target_file);
ian@0 277 target_duration_ = extractor_.Duration();
ian@0 278 std::for_each(features_.begin(), features_.end(),
ian@0 279 boost::bind(&FeatureComparerInterface::AnalyzeTarget, _1));
ian@0 280 }
ian@0 281 }
ian@0 282
ian@0 283
ian@0 284 Value FileComparer::CompareFile(const std::string& file_path) {
ian@0 285 extractor_.LoadFile(file_path);
ian@0 286 Value error;
ian@0 287 foreach (FeatureComparerPtr& feature, features_) {
ian@0 288 error += feature->Compare();
ian@0 289 }
ian@0 290 return error / features_.size();
ian@0 291 }
ian@0 292
ian@0 293 namespace {
ian@0 294
ian@0 295 // Helper function for creating FeatureComparers
ian@0 296 template<typename T>
ian@0 297 FeatureComparerPtr MakeFeatureComparer(FileComparer::Feature::ID id,
ian@0 298 FeatureExtractor& extractor,
ian@0 299 const T& (FeatureExtractor::*feature)(),
ian@0 300 void (*converter)(T&) = NULL,
ian@0 301 bool scale_by_energy = false) {
ian@0 302 return FeatureComparerPtr(new FeatureComparer<T>(static_cast<int>(id),
ian@0 303 &extractor,
ian@0 304 feature,
ian@0 305 converter,
ian@0 306 scale_by_energy));
ian@0 307 }
ian@0 308
ian@0 309 } // namespace
ian@0 310
ian@0 311 void FileComparer::EnableFeature(Feature::ID feature_id, bool enable) {
ian@0 312 // check if the requested feature is already enabled
ian@0 313 for (std::vector<FeatureComparerPtr>::iterator feature = features_.begin(),
ian@0 314 end = features_.end();
ian@0 315 feature != end;
ian@0 316 ++feature) {
ian@0 317 if ((*feature)->ID() == feature_id) {
ian@0 318 // feature enabled, if enable == false then remove the feature
ian@0 319 if (!enable) {
ian@0 320 features_.erase(feature);
ian@0 321 }
ian@0 322 return;
ian@0 323 }
ian@0 324 }
ian@0 325 // make the feature comparer
ian@0 326 FeatureComparerPtr feature;
ian@0 327 switch (feature_id) {
ian@0 328 case Feature::Pitch:
ian@0 329 feature = MakeFeatureComparer(feature_id,
ian@0 330 extractor_,
ian@0 331 &FeatureExtractor::Pitch,
ian@0 332 ConvertFrequencyList,
ian@0 333 true);
ian@0 334 break;
ian@0 335 case Feature::Energy:
ian@0 336 feature = MakeFeatureComparer(feature_id,
ian@0 337 extractor_,
ian@0 338 &FeatureExtractor::Energy);
ian@0 339 break;
ian@0 340 case Feature::MFCCs:
ian@0 341 feature = MakeFeatureComparer(feature_id,
ian@0 342 extractor_,
ian@0 343 &FeatureExtractor::MFCCs);
ian@0 344 break;
ian@0 345 case Feature::DeltaMFCCs:
ian@0 346 feature = MakeFeatureComparer(feature_id,
ian@0 347 extractor_,
ian@0 348 &FeatureExtractor::DeltaMFCCs);
ian@0 349 break;
ian@0 350 case Feature::DoubleDeltaMFCCs:
ian@0 351 feature = MakeFeatureComparer(feature_id,
ian@0 352 extractor_,
ian@0 353 &FeatureExtractor::DoubleDeltaMFCCs);
ian@0 354 break;
ian@0 355 case Feature::Magnitude:
ian@0 356 feature = MakeFeatureComparer(feature_id,
ian@0 357 extractor_,
ian@0 358 &FeatureExtractor::MagnitudeSpectrum);
ian@0 359 case Feature::LogMagnitude:
ian@0 360 feature = MakeFeatureComparer(feature_id,
ian@0 361 extractor_,
ian@0 362 &FeatureExtractor::LogMagnitudeSpectrum);
ian@0 363 break;
ian@0 364 case Feature::SpectralCentroid:
ian@0 365 feature = MakeFeatureComparer(feature_id,
ian@0 366 extractor_,
ian@0 367 &FeatureExtractor::SpectralCentroid,
ian@0 368 ConvertFrequencyList,
ian@0 369 true);
ian@0 370 break;
ian@0 371 case Feature::SpectralSpread:
ian@0 372 feature = MakeFeatureComparer(feature_id,
ian@0 373 extractor_,
ian@0 374 &FeatureExtractor::SpectralSpread,
ian@0 375 ConvertFrequencyList,
ian@0 376 true);
ian@0 377 break;
ian@0 378 case Feature::SpectralFlux:
ian@0 379 feature = MakeFeatureComparer(feature_id,
ian@0 380 extractor_,
ian@0 381 &FeatureExtractor::SpectralFlux);
ian@0 382 break;
ian@0 383 default:
ian@0 384 throw std::runtime_error("FileComparer::EnableFeature - Invalid ID");
ian@0 385 }
ian@0 386 // analyze the feature if we already have a target file loaded
ian@0 387 if (!target_file_.empty()) {
ian@0 388 feature->AnalyzeTarget();
ian@0 389 }
ian@0 390 // store the feature
ian@0 391 features_.push_back(feature);
ian@0 392 }
ian@0 393
ian@0 394 void FileComparer::EnableFeatures(const std::vector<Feature::ID>& features,
ian@0 395 bool enable /* = true */) {
ian@0 396 foreach (Feature::ID id, features) {
ian@0 397 EnableFeature(id, enable);
ian@0 398 }
ian@0 399 }
ian@0 400
ian@0 401 void FileComparer::EnableFeatures(std::string feature_name_list,
ian@0 402 bool enable /* = true */) {
ian@0 403 std::vector<std::string> feature_names;
ian@0 404 // split the comma separated list of names
ian@0 405 boost::algorithm::split(feature_names,
ian@0 406 feature_name_list,
ian@0 407 boost::algorithm::is_any_of(", "),
ian@0 408 boost::algorithm::token_compress_on);
ian@0 409 // enable each feature
ian@0 410 foreach (const std::string& feature_name, feature_names) {
ian@0 411 // check if the requested feature name is valid
ian@0 412 if (feature_names_.find(feature_name) == feature_names_.end()) {
ian@0 413 std::stringstream message;
ian@0 414 message << "FileComparer::EnableFeatures - Unknown feature '"
ian@0 415 << feature_name << "'";
ian@0 416 throw std::runtime_error(message.str());
ian@0 417 }
ian@0 418 EnableFeature(feature_names_[feature_name], enable);
ian@0 419 }
ian@0 420 }
ian@0 421
ian@0 422 void FileComparer::SetFeatures(const std::vector<Feature::ID>& features) {
ian@0 423 features_.clear();
ian@0 424 EnableFeatures(features);
ian@0 425 }
ian@0 426
ian@0 427 } // dsp namespace