annotate src/sc_evaluator.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 "sc_evaluator.hpp"
ian@0 20
ian@0 21 #include "boost_ex.hpp"
ian@0 22 #include "logger.hpp"
ian@0 23 #include "sc_converter.hpp"
ian@0 24 #include "statistics.hpp"
ian@0 25
ian@0 26 #include "boost/filesystem.hpp"
ian@0 27 #include "boost/format.hpp"
ian@0 28 #include "boost/lexical_cast.hpp"
ian@0 29 #include "boost/process.hpp"
ian@0 30 #include "boost/thread.hpp"
ian@0 31 #include "boost/math/special_functions/fpclassify.hpp"
ian@0 32
ian@0 33 #include <algorithm>
ian@0 34 #include <cmath>
ian@0 35 #include <functional>
ian@0 36 #include <numeric>
ian@0 37
ian@0 38 namespace bp = boost::process;
ian@0 39
ian@0 40 namespace sc {
ian@0 41
ian@0 42 const int g_max_file_wait_time_ms = 10000;
ian@0 43 const int g_file_wait_increment_ms = 500;
ian@0 44 const int g_sclang_timeout_s = 10;
ian@0 45 const double g_fitness_failure_value = 100;
ian@0 46 const std::string g_score_variable_name = "x";
ian@0 47
ian@0 48 // Worker class, runs in own thread
ian@0 49 class EvaluatorWorker {
ian@0 50 sc::Converter converter_;
ian@0 51 int thread_id_;
ian@0 52 bool quit_threads_;
ian@0 53 Evaluator* parent_;
ian@0 54 dsp::FileComparer file_comparer_;
ian@0 55 // work thread communicates with parent, handles work queue
ian@0 56 boost::thread work_thread_;
ian@0 57 // render thread launches child processes
ian@0 58 boost::thread render_thread_;
ian@0 59 boost::mutex render_mutex_;
ian@0 60 // render duration in seconds
ian@0 61 double render_length_;
ian@0 62 std::string render_command_path_;
ian@0 63 std::string render_osc_path_;
ian@0 64 std::string render_output_path_;
ian@0 65 // flag for render thread that a synth is ready for rendering
ian@0 66 bool do_render_;
ian@0 67 // condition to notify render thread that work is available
ian@0 68 boost::condition_variable render_start_;
ian@0 69 // condition to notify work thread that work has been done
ian@0 70 boost::condition_variable render_finished_;
ian@0 71 // stores handle to active child process
ian@0 72 boost::shared_ptr<bp::child> render_process_;
ian@0 73
ian@0 74 public:
ian@0 75 EvaluatorWorker(Evaluator* parent,
ian@0 76 int thread_id,
ian@0 77 const dsp::FileComparer& target)
ian@0 78 : parent_(parent),
ian@0 79 thread_id_(thread_id),
ian@0 80 converter_(parent->grammar_),
ian@0 81 quit_threads_(false),
ian@0 82 file_comparer_(target),
ian@0 83 render_length_(file_comparer_.TargetDuration()),
ian@0 84 do_render_(false)
ian@0 85 {
ian@0 86 work_thread_ = boost::thread(&EvaluatorWorker::DoWork, this);
ian@0 87 render_thread_ = boost::thread(&EvaluatorWorker::DoSCRender, this);
ian@0 88 }
ian@0 89
ian@0 90 ~EvaluatorWorker() {
ian@0 91 // tell threads to quit
ian@0 92 quit_threads_ = true;
ian@0 93 // work thread
ian@0 94 try {
ian@0 95 boost::system_time timeout = boost::get_system_time();
ian@0 96 timeout += boost::posix_time::seconds(g_sclang_timeout_s);
ian@0 97 parent_->queue_condition_.notify_all();
ian@0 98 work_thread_.timed_join(timeout);
ian@0 99 // render thread
ian@0 100 timeout = boost::get_system_time();
ian@0 101 timeout += boost::posix_time::seconds(g_sclang_timeout_s);
ian@0 102 render_start_.notify_one();
ian@0 103 render_thread_.timed_join(timeout);
ian@0 104 }
ian@0 105 catch (const std::exception& e) {
ian@0 106 std::stringstream message;
ian@0 107 message << "~sc::EvaluatorWorker - Exception: " << e.what() << '\n';
ian@0 108 WorkerLog(message.str());
ian@0 109 }
ian@0 110 }
ian@0 111
ian@0 112 private:
ian@0 113
ian@0 114 void DoWork() {
ian@0 115 while (!quit_threads_) {
ian@0 116 // wait for some work
ian@0 117 {
ian@0 118 boost::mutex::scoped_lock lock(parent_->work_mutex_);
ian@0 119 while (parent_->work_queue_.empty() && !quit_threads_) {
ian@0 120 parent_->queue_condition_.wait(lock);
ian@0 121 }
ian@0 122 }
ian@0 123 // keep going until the work queue is empty
ian@0 124 while (!quit_threads_) {
ian@0 125 sg::Graph* graph = NULL;
ian@0 126 {
ian@0 127 // check there's still work to do
ian@0 128 boost::mutex::scoped_lock lock(parent_->work_mutex_);
ian@0 129 if (parent_->work_queue_.empty()) {
ian@0 130 // wait for all threads to reach this point before continuing
ian@0 131 lock.unlock();
ian@0 132 parent_->work_done_barrier_->wait();
ian@0 133 lock.lock();
ian@0 134 // notify main thread that work's finished
ian@0 135 parent_->work_done_condition_.notify_one();
ian@0 136 break;
ian@0 137 }
ian@0 138 // get the next graph from the queue
ian@0 139 graph = parent_->work_queue_.front();
ian@0 140 parent_->work_queue_.pop();
ian@0 141 }
ian@0 142 // get the graph properties
ian@0 143 sg::Graph& graph_ref = *graph;
ian@0 144 sg::GraphProperties& graph_properties = graph_ref[boost::graph_bundle];
ian@0 145 double fitness;
ian@0 146 try {
ian@0 147 // rate the graph
ian@0 148 fitness = RateGraph(graph_ref);
ian@0 149 } catch (const std::exception& e) {
ian@0 150 std::stringstream message;
ian@0 151 message << "Exception: " << e.what() << '\n';
ian@0 152 WorkerLog(message.str());
ian@0 153 fitness = g_fitness_failure_value;
ian@0 154 }
ian@0 155 if (boost::math::isinf(fitness)) {
ian@0 156 fitness = g_fitness_failure_value;
ian@0 157 } else if (boost::math::isnan(fitness)) {
ian@0 158 fitness = g_fitness_failure_value;
ian@0 159 }
ian@0 160 graph_properties.fitness_ = fitness;
ian@0 161 // std::stringstream message;
ian@0 162 // message << "Rated " << graph_properties.id_ << ":" << fitness << '\n';
ian@0 163 // WorkerLog(message.str());
ian@0 164 {
ian@0 165 // decrement the graph queue count
ian@0 166 boost::mutex::scoped_lock lock(parent_->work_mutex_);
ian@0 167 parent_->graphs_to_rate_--;
ian@0 168 parent_->work_done_condition_.notify_one();
ian@0 169 }
ian@0 170 }
ian@0 171 }
ian@0 172 }
ian@0 173
ian@0 174 void WorkerLog(const std::string& message) {
ian@0 175 std::stringstream formatter;
ian@0 176 formatter << "\n[" << thread_id_ << "] " << message;
ian@0 177 logger::Log(formatter.str());
ian@0 178 }
ian@0 179
ian@0 180 double RateGraph(sg::Graph& graph) {
ian@0 181 int id = graph[boost::graph_bundle].id_;
ian@0 182 std::stringstream name;
ian@0 183 name << "gpsynth_"
ian@0 184 << std::setw(parent_->synth_name_zero_pad_length_) << std::setfill('0')
ian@0 185 << id;
ian@0 186 std::string synth_name = name.str();
ian@0 187 // convert the graph to a synthdef and score
ian@0 188 std::string synthdef = converter_.ToSynthDef(graph,
ian@0 189 synth_name,
ian@0 190 false,
ian@0 191 parent_->synthdef_folder_);
ian@0 192 std::string score = converter_.ToScore(graph,
ian@0 193 synth_name,
ian@0 194 g_score_variable_name,
ian@0 195 false, // prepare for non-realtime
ian@0 196 parent_->synthdef_folder_,
ian@0 197 render_length_);
ian@0 198 {
ian@0 199 // prepare the synthdef and score for the render thread
ian@0 200 boost::mutex::scoped_lock lock(render_mutex_);
ian@0 201 render_command_path_ = parent_->sc_folder_ + synth_name + ".sc";
ian@0 202 render_osc_path_ = parent_->osc_folder_ + synth_name + ".osc";
ian@0 203 render_output_path_ = parent_->audio_folder_+ synth_name + ".aiff";
ian@0 204 // prepare render script for sclang
ian@0 205 std::ofstream command_file(render_command_path_.c_str());
ian@0 206 // write the synthdef and the score
ian@0 207 command_file << synthdef << '\n' << score;
ian@0 208 // tell sclang to write the score as OSC data
ian@0 209 command_file << "Score.write(" << g_score_variable_name << ", \""
ian@0 210 << render_osc_path_ << "\");\n";
ian@0 211 // tell sclang to quit at end of script
ian@0 212 command_file << "\n0.exit;";
ian@0 213 // finished, close the file
ian@0 214 command_file.close();
ian@0 215 // tell the render thread that work is available
ian@0 216 do_render_ = true;
ian@0 217 boost::system_time timeout = boost::get_system_time();
ian@0 218 timeout += boost::posix_time::seconds(g_sclang_timeout_s);
ian@0 219 render_start_.notify_one();
ian@0 220 // wait for the render thread to complete the job
ian@0 221 if (!render_finished_.timed_wait(lock, timeout)) {
ian@0 222 // sclang or scsynth timed out, kill process and set fitness to failure
ian@0 223 // WorkerLog("Render thread timed out.\n");
ian@0 224 try {
ian@0 225 render_process_->terminate();
ian@0 226 }
ian@0 227 catch (boost::system::system_error& e) {
ian@0 228 // don't need to do anything here, process may already be terminated.
ian@0 229 }
ian@0 230 do_render_ = false;
ian@0 231 // maximum error
ian@0 232 return g_fitness_failure_value;
ian@0 233 }
ian@0 234 }
ian@0 235 // check that a rendered file has been produced
ian@0 236 if (!boost::filesystem::exists(render_output_path_)) {
ian@0 237 // file missing, something's gone wrong..
ian@0 238 WorkerLog("Render output missing.\n");
ian@0 239 return g_fitness_failure_value;
ian@0 240 }
ian@0 241 // store render path with graph for future reference
ian@0 242 graph[boost::graph_bundle].render_path_ = render_output_path_;
ian@0 243 // compare the rendered file to the target
ian@0 244 return CompareToTarget(render_output_path_);
ian@0 245 }
ian@0 246
ian@0 247 void DoSCRender() {
ian@0 248 std::vector<std::string> args;
ian@0 249 while (!quit_threads_) {
ian@0 250 // wait for a command to render
ian@0 251 {
ian@0 252 boost::mutex::scoped_lock lock(render_mutex_);
ian@0 253 do_render_ = false;
ian@0 254 while (!do_render_ && !quit_threads_) {
ian@0 255 render_start_.wait(lock);
ian@0 256 }
ian@0 257 if (quit_threads_) {
ian@0 258 return;
ian@0 259 }
ian@0 260 }
ian@0 261 // Call sclang to get OSC command for scsynth
ian@0 262 args.clear();
ian@0 263 args.push_back("-d");
ian@0 264 args.push_back(parent_->sc_app_path_);
ian@0 265 args.push_back(render_command_path_);
ian@0 266 // replace process pipes to suppress output
ian@0 267 bp::context closed_streams;
ian@0 268 closed_streams.streams[bp::stdout_id] = bp::behavior::close();
ian@0 269 closed_streams.streams[bp::stderr_id] = bp::behavior::close();
ian@0 270 bp::child process = bp::create_child(parent_->sclang_path_,
ian@0 271 args,
ian@0 272 closed_streams);
ian@0 273 // store process handle to allow work thread to kill process if it's hung
ian@0 274 {
ian@0 275 boost::mutex::scoped_lock lock(render_mutex_);
ian@0 276 render_process_.reset(new bp::child(process));
ian@0 277 }
ian@0 278 // wait for sclang to finish
ian@0 279 process.wait();
ian@0 280 if (!do_render_) {
ian@0 281 // if do_render_ is false, process was killed by work thread
ian@0 282 continue;
ian@0 283 }
ian@0 284 // check that sclang generated an osc command
ian@0 285 if (!boost::filesystem::exists(render_osc_path_)) {
ian@0 286 // sclang failed, notify work thread and continue
ian@0 287 boost::mutex::scoped_lock lock(render_mutex_);
ian@0 288 render_finished_.notify_one();
ian@0 289 continue;
ian@0 290 }
ian@0 291 // render osc with scsynth
ian@0 292 args.clear();
ian@0 293 args.push_back("-N");
ian@0 294 args.push_back(render_osc_path_);
ian@0 295 args.push_back("_"); // no input file
ian@0 296 args.push_back(render_output_path_);
ian@0 297 args.push_back("44100");
ian@0 298 args.push_back("AIFF");
ian@0 299 args.push_back("int16");
ian@0 300 // output channels
ian@0 301 args.push_back("-o");
ian@0 302 args.push_back("1");
ian@0 303 // don't publish to rendevous
ian@0 304 args.push_back("-R");
ian@0 305 args.push_back("0");
ian@0 306 // don't load synthdefs
ian@0 307 args.push_back("-D");
ian@0 308 args.push_back("0");
ian@0 309 // plugins path
ian@0 310 args.push_back("-U");
ian@0 311 args.push_back(parent_->sc_plugins_path_);
ian@0 312 // call scsynth
ian@0 313 process = bp::create_child(parent_->scsynth_path_, args, closed_streams);
ian@0 314 {
ian@0 315 boost::mutex::scoped_lock lock(render_mutex_);
ian@0 316 render_process_.reset(new bp::child(process));
ian@0 317 }
ian@0 318 // wait for scsynth to finish rendering
ian@0 319 process.wait();
ian@0 320 if (!do_render_) {
ian@0 321 // process timed out, killed by work thread
ian@0 322 continue;
ian@0 323 }
ian@0 324 // job done, notify work thread
ian@0 325 {
ian@0 326 boost::mutex::scoped_lock lock(render_mutex_);
ian@0 327 render_finished_.notify_one();
ian@0 328 }
ian@0 329 }
ian@0 330 }
ian@0 331
ian@0 332 double CompareToTarget(const std::string& file_path) {
ian@0 333 return file_comparer_.CompareFile(file_path);
ian@0 334 }
ian@0 335 };
ian@0 336
ian@0 337
ian@0 338 ////////////////////////////////////////////////////////////////////////////////
ian@0 339 ////////////////////////////////////////////////////////////////////////////////
ian@0 340
ian@0 341 Evaluator::Evaluator(const sc::Grammar& grammar,
ian@0 342 const std::string& sc_app_path,
ian@0 343 const dsp::FileComparer& target,
ian@0 344 int core_limit /* = 0 */)
ian@0 345 : grammar_(grammar),
ian@0 346 listener_(NULL),
ian@0 347 sc_app_path_(sc_app_path),
ian@0 348 sc_plugins_path_(sc_app_path + "/plugins"),
ian@0 349 sclang_path_(sc_app_path + "/sclang"),
ian@0 350 scsynth_path_(sc_app_path + "/scsynth")
ian@0 351 {
ian@0 352 SetWorkFolder("/tmp/");
ian@0 353 int workers = boost::thread::hardware_concurrency();
ian@0 354 if (core_limit > 0 && core_limit < workers) {
ian@0 355 workers = core_limit;
ian@0 356 }
ian@0 357 work_done_barrier_.reset(new boost::barrier(workers));
ian@0 358 for (int worker_id = 0; worker_id < workers; worker_id++) {
ian@0 359 workers_.push_back(EvaluatorWorkerPtr(new EvaluatorWorker(this,
ian@0 360 worker_id,
ian@0 361 target)));
ian@0 362 }
ian@0 363 }
ian@0 364
ian@0 365 Evaluator::~Evaluator()
ian@0 366 {}
ian@0 367
ian@0 368
ian@0 369 void Evaluator::RateGraphs(std::vector<sg::Graph>& graphs) {
ian@0 370 // make sure necessary subfolders exist in work folder
ian@0 371 audio_folder_ = work_folder_ + "/audio/";
ian@0 372 osc_folder_ = work_folder_ + "/osc/";
ian@0 373 sc_folder_ = work_folder_ + "/sc/";
ian@0 374 synthdef_folder_ = work_folder_ + "/synthdefs/";
ian@0 375 boost::filesystem::create_directories(audio_folder_);
ian@0 376 boost::filesystem::create_directories(osc_folder_);
ian@0 377 boost::filesystem::create_directories(sc_folder_);
ian@0 378 boost::filesystem::create_directories(synthdef_folder_);
ian@0 379 // find zero padding length for synth names
ian@0 380 synth_name_zero_pad_length_ = std::log10(graphs.size()) + 1;
ian@0 381 // store the graphs in the work queue
ian@0 382 boost::mutex::scoped_lock lock(work_mutex_);
ian@0 383 graphs_to_rate_ = static_cast<int>(graphs.size());
ian@0 384 for (int i = 0; i < graphs_to_rate_; i++) {
ian@0 385 work_queue_.push(&graphs[i]);
ian@0 386 }
ian@0 387 // wait for graphs to be rated
ian@0 388 std::size_t last_rated_count = 0;
ian@0 389 while (graphs_to_rate_ > 0) {
ian@0 390 queue_condition_.notify_all();
ian@0 391 work_done_condition_.wait(lock);
ian@0 392 if (listener_ != NULL) {
ian@0 393 std::size_t rated_count = graphs.size() - graphs_to_rate_;
ian@0 394 if (rated_count != last_rated_count) {
ian@0 395 last_rated_count = rated_count;
ian@0 396 listener_->GraphRatedNotification(rated_count);
ian@0 397 }
ian@0 398 }
ian@0 399 }
ian@0 400 // remove OSC folder, no longer required
ian@0 401 boost::filesystem::remove_all(osc_folder_);
ian@0 402 }
ian@0 403
ian@0 404 void Evaluator::SetWorkFolder(const std::string& path) {
ian@0 405 work_folder_ = path;
ian@0 406 }
ian@0 407
ian@0 408 } // sc namespace