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