Make Analyzer UI require instance-access
[calf.git] / src / host_session.cpp
blob1951acc13e99fb660aec714bdfea4b4b1781e972
1 /* Calf DSP Library Utility Application - calfjackhost
2 * A class that contains a JACK host session
4 * Copyright (C) 2007-2011 Krzysztof Foltman
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2, or (at your option)
9 * any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 * 02110-1301, USA.
22 #include <calf/giface.h>
23 #include <calf/host_session.h>
24 #include <calf/gui.h>
25 #include <calf/preset.h>
26 #include <getopt.h>
27 #include <sys/stat.h>
29 using namespace std;
30 using namespace calf_utils;
31 using namespace calf_plugins;
33 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
35 host_session *host_session::instance = NULL;
37 host_session::host_session(session_environment_iface *se)
39 client_name = "Calf Studio Gear";
40 calfjackhost_cmd = "calfjackhost";
41 session_env = se;
42 autoconnect_midi_index = -1;
43 gui_win = NULL;
44 session_manager = NULL;
45 only_load_if_exists = false;
46 save_file_on_next_idle_call = false;
47 quit_on_next_idle_call = 0;
48 handle_event_on_next_idle_call = NULL;
50 main_win = session_env->create_main_window();
51 main_win->set_owner(this);
54 std::string host_session::get_next_instance_name(const std::string &effect_name)
56 if (!instances.count(effect_name))
57 return effect_name;
58 for (int i = 2; ; i++)
60 string tmp = string(effect_name) + " (" + i2s(i) + ")";
61 if (!instances.count(tmp))
62 return tmp;
64 assert(0);
65 return "-";
68 void host_session::add_plugin(string name, string preset, string instance_name)
70 if (instance_name.empty())
71 instance_name = get_next_instance_name(name);
72 jack_host *jh = create_jack_host(&client, name.c_str(), instance_name, main_win);
73 if (!jh) {
74 string s =
75 #define PER_MODULE_ITEM(name, isSynth, jackname) jackname ", "
76 #include <calf/modulelist.h>
78 if (!s.empty())
79 s = s.substr(0, s.length() - 2);
80 throw text_exception("Unknown plugin name; allowed are: " + s);
82 instances.insert(jh->instance_name);
83 jh->create();
85 plugins.push_back(jh);
86 client.add(jh);
87 main_win->add_plugin(jh);
88 if (!preset.empty()) {
89 if (!activate_preset(plugins.size() - 1, preset, false))
91 if (!activate_preset(plugins.size() - 1, preset, true))
93 fprintf(stderr, "Unknown preset: %s\n", preset.c_str());
99 void host_session::create_plugins_from_list()
101 for (unsigned int i = 0; i < plugin_names.size(); i++) {
102 add_plugin(plugin_names[i], presets.count(i) ? presets[i] : string());
106 void host_session::on_main_window_destroy()
108 session_env->quit_gui_loop();
111 void host_session::open()
113 if (!input_name.empty()) client.input_name = input_name;
114 if (!output_name.empty()) client.output_name = output_name;
115 if (!midi_name.empty()) client.midi_name = midi_name;
117 client.open(client_name.c_str(), !jack_session_id.empty() ? jack_session_id.c_str() : NULL);
118 jack_set_session_callback(client.client, session_callback, this);
119 main_win->add_condition("jackhost");
120 main_win->add_condition("directlink");
121 main_win->add_condition("configure");
122 client.create_automation_input();
123 if (!session_manager || !session_manager->is_being_restored())
124 create_plugins_from_list();
125 main_win->create();
128 void host_session::session_callback(jack_session_event_t *event, void *arg)
130 printf("session callback type %d\n", (int)event->type);
131 switch(event->type)
133 case JackSessionSave:
134 case JackSessionSaveAndQuit:
135 case JackSessionSaveTemplate:
136 host_session *hs = (host_session *)arg;
137 hs->handle_event_on_next_idle_call = event;
139 // XXXKF if more than one event happen in a short sequence, the other event
140 // may be lost. This calls for implementing a proper event queue.
143 void host_session::handle_jack_session_event(jack_session_event_t *event)
145 try {
146 asprintf(&event->command_line, "%s --load ${SESSION_DIR}" G_DIR_SEPARATOR_S "rack.xml --session-id %s" , calfjackhost_cmd.c_str(), event->client_uuid);
147 string fn = event->session_dir;
148 fn += "rack.xml";
149 save_file(fn.c_str());
151 catch(...)
153 event->flags = JackSessionSaveError;
154 // let the server know that the save operation failed
155 jack_session_reply(client.client, event);
156 jack_session_event_free(event);
157 throw;
160 if (event->type == JackSessionSaveAndQuit)
161 quit_on_next_idle_call = 1;
162 jack_session_reply(client.client, event);
163 jack_session_event_free(event);
166 void host_session::new_plugin(const char *name)
168 jack_host *jh = create_jack_host(&client, name, get_next_instance_name(name), main_win);
169 if (!jh)
170 return;
171 instances.insert(jh->instance_name);
172 jh->create();
174 plugins.push_back(jh);
175 client.add(jh);
176 main_win->add_plugin(jh);
179 void host_session::remove_plugin(plugin_ctl_iface *plugin)
181 for (unsigned int i = 0; i < plugins.size(); i++)
183 if (plugins[i] == plugin)
185 instances.erase(plugins[i]->instance_name);
186 client.del(plugins[i]);
187 plugins.erase(plugins.begin() + i);
188 main_win->del_plugin(plugin);
189 delete plugin;
190 return;
195 void host_session::remove_all_plugins()
197 while(!plugins.empty())
199 jack_host *plugin = plugins[0];
200 client.del(plugins[0]);
201 plugins.erase(plugins.begin());
202 main_win->del_plugin(plugin);
203 delete plugin;
205 instances.clear();
208 bool host_session::activate_preset(int plugin_no, const std::string &preset, bool builtin)
210 string cur_plugin = plugins[plugin_no]->metadata->get_id();
211 preset_vector &pvec = (builtin ? get_builtin_presets() : get_user_presets()).presets;
212 for (unsigned int i = 0; i < pvec.size(); i++) {
213 if (pvec[i].name == preset && pvec[i].plugin == cur_plugin)
215 pvec[i].activate(plugins[plugin_no]);
216 if (gui_win && gui_win->gui)
217 gui_win->gui->refresh();
218 return true;
221 return false;
224 void host_session::connect()
226 client.activate();
227 if (session_manager)
228 session_manager->set_jack_client_name(client.get_name());
229 if ((!session_manager || !session_manager->is_being_restored()) && load_name.empty())
231 string cnp = client.get_name() + ":";
232 for (unsigned int i = 0; i < plugins.size(); i++) {
233 if (chains.count(i)) {
234 if (!i)
236 if (plugins[0]->metadata->get_input_count() < 2)
238 fprintf(stderr, "Cannot connect input to plugin %s - the plugin no input ports\n", plugins[0]->name.c_str());
239 } else {
240 client.connect("system:capture_1", cnp + plugins[0]->get_inputs()[0].name);
241 client.connect("system:capture_2", cnp + plugins[0]->get_inputs()[1].name);
244 else
246 if (plugins[i - 1]->metadata->get_output_count() < 2 || plugins[i]->metadata->get_input_count() < 2)
248 fprintf(stderr, "Cannot connect plugins %s and %s - incompatible ports\n", plugins[i - 1]->name.c_str(), plugins[i]->name.c_str());
250 else {
251 client.connect(cnp + plugins[i - 1]->get_outputs()[0].name, cnp + plugins[i]->get_inputs()[0].name);
252 client.connect(cnp + plugins[i - 1]->get_outputs()[1].name, cnp + plugins[i]->get_inputs()[1].name);
257 if (chains.count(plugins.size()) && plugins.size())
259 int last = plugins.size() - 1;
260 if (plugins[last]->metadata->get_output_count() < 2)
262 fprintf(stderr, "Cannot connect plugin %s to output - incompatible ports\n", plugins[last]->name.c_str());
263 } else {
264 client.connect(cnp + plugins[last]->get_outputs()[0].name, "system:playback_1");
265 client.connect(cnp + plugins[last]->get_outputs()[1].name, "system:playback_2");
268 if (autoconnect_midi != "") {
269 for (unsigned int i = 0; i < plugins.size(); i++)
271 if (plugins[i]->metadata->get_midi())
272 client.connect(autoconnect_midi, cnp + plugins[i]->get_midi_port()->name);
275 else
276 if (autoconnect_midi_index != -1) {
277 const char **ports = client.get_ports(".*:.*", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput | JackPortIsPhysical);
278 for (int j = 0; ports && ports[j]; j++)
280 if (j + 1 == autoconnect_midi_index) {
281 for (unsigned int i = 0; i < plugins.size(); i++)
283 if (plugins[i]->metadata->get_midi())
284 client.connect(ports[j], cnp + plugins[i]->get_midi_port()->name);
286 break;
289 free(ports);
292 if (!load_name.empty())
294 char *error = open_file(load_name.c_str());
295 if (error)
297 bool suppress_error = false;
298 if (only_load_if_exists)
300 struct stat s;
301 int stat_result = stat(load_name.c_str(), &s);
302 if (stat_result == -1 && errno == ENOENT)
303 suppress_error = true;
305 // If the file is optional and it didn't exist, suppress the error
306 if (!suppress_error)
308 main_win->show_error("Cannot load '" + load_name + "': " + error);
310 load_name = "";
312 g_free(error);
314 set_current_filename(load_name);
316 if (session_manager)
317 session_manager->connect("calf-" + client_name);
320 void host_session::close()
322 if (session_manager)
323 session_manager->disconnect();
324 main_win->on_closed();
325 client.deactivate();
326 client.delete_plugins();
327 client.destroy_automation_input();
328 client.close();
331 static string stripfmt(string x)
333 if (x.length() < 2)
334 return x;
335 if (x.substr(x.length() - 2) != "%d")
336 return x;
337 return x.substr(0, x.length() - 2);
340 char *host_session::open_file(const char *name)
342 preset_list pl;
343 try {
344 remove_all_plugins();
345 pl.load(name, true);
346 printf("Size %d\n", (int)pl.plugins.size());
347 for (unsigned int i = 0; i < pl.plugins.size(); i++)
349 preset_list::plugin_snapshot &ps = pl.plugins[i];
350 client.input_nr = ps.input_index;
351 client.output_nr = ps.output_index;
352 client.midi_nr = ps.midi_index;
353 printf("Loading %s\n", ps.type.c_str());
354 if (ps.preset_offset < (int)pl.presets.size())
356 add_plugin(ps.type, "", ps.instance_name);
357 pl.presets[ps.preset_offset].activate(plugins[i]);
358 for (size_t j = 0; j < ps.automation_entries.size(); ++j)
360 const pair<string, string> &p = ps.automation_entries[j];
361 plugins[i]->configure(p.first.c_str(), p.second.c_str());
363 main_win->refresh_plugin(plugins[i]);
367 catch(preset_exception &e)
369 // XXXKF this will leak
370 char *data = strdup(e.what());
371 return data;
374 return NULL;
377 struct gather_automation_params: public send_configure_iface
379 stringstream *xml;
380 dictionary *dict;
381 gather_automation_params(stringstream &_xml)
382 : xml(&_xml)
383 , dict(NULL)
385 gather_automation_params(dictionary &_dict)
386 : xml(NULL)
387 , dict(&_dict)
390 virtual void send_configure(const char *key, const char *value)
392 if (xml)
393 *xml << "<automation key=\"" << key << "\" value=\"" << value << "\" />" << endl;
394 if (dict && key && value)
395 (*dict)[key] = value;
399 char *host_session::save_file(const char *name)
401 string i_name = stripfmt(client.input_name);
402 string o_name = stripfmt(client.output_name);
403 string m_name = stripfmt(client.midi_name);
404 stringstream data;
405 data << "<?xml version=\"1.1\" encoding=\"utf-8\"?>" << endl;
406 data << "<rack>";
407 for (unsigned int i = 0; i < plugins.size(); i++) {
408 jack_host *p = plugins[i];
409 plugin_preset preset;
410 preset.plugin = p->metadata->get_id();
411 preset.get_from(p);
412 data << "<plugin";
413 data << to_xml_attr("type", preset.plugin);
414 data << to_xml_attr("instance-name", p->instance_name);
415 if (p->metadata->get_input_count())
416 data << to_xml_attr("input-index", p->get_inputs()[0].name.substr(i_name.length()));
417 if (p->metadata->get_output_count())
418 data << to_xml_attr("output-index", p->get_outputs()[0].name.substr(o_name.length()));
419 if (p->get_midi_port())
420 data << to_xml_attr("midi-index", p->get_midi_port()->name.substr(m_name.length()));
421 data << ">" << endl;
422 data << preset.to_xml();
423 gather_automation_params gap(data);
424 p->send_automation_configures(&gap);
425 data << "</plugin>" << endl;
427 data << "</rack>" << endl;
428 string datastr(data.str());
429 FILE *f = fopen(name, "w");
430 if (!f || 1 != fwrite(datastr.c_str(), datastr.length(), 1, f))
432 int e = errno;
433 if (f)
434 fclose(f);
435 return strdup(strerror(e));
437 if (fclose(f))
438 return strdup(strerror(errno));
440 return NULL;
443 void host_session::load(session_load_iface *stream)
445 // printf("!!!Restore data set!!!\n");
446 remove_all_plugins();
447 string key, data;
448 while(stream->get_next_item(key, data)) {
449 if (key == "global")
451 dictionary dict;
452 decode_map(dict, data);
453 if (dict.count("input_prefix")) client.input_name = dict["input_prefix"]+"%d";
454 if (dict.count("output_prefix")) client.output_name = dict["output_prefix"]+"%d";
455 if (dict.count("midi_prefix")) client.midi_name = dict["midi_prefix"]+"%d";
457 if (!strncmp(key.c_str(), "Plugin", 6))
459 unsigned int nplugin = atoi(key.c_str() + 6);
460 dictionary dict, automation;
461 decode_map(dict, data);
462 data = dict["preset"];
463 if (dict.count("automation"))
464 decode_map(automation, dict["automation"]);
465 string instance_name;
466 if (dict.count("instance_name")) instance_name = dict["instance_name"];
467 if (dict.count("input_name")) client.input_nr = atoi(dict["input_name"].c_str());
468 if (dict.count("output_name")) client.output_nr = atoi(dict["output_name"].c_str());
469 if (dict.count("midi_name")) client.midi_nr = atoi(dict["midi_name"].c_str());
470 preset_list tmp;
471 tmp.parse("<presets>"+data+"</presets>", false);
472 if (tmp.presets.size())
474 printf("Load plugin %s\n", tmp.presets[0].plugin.c_str());
475 add_plugin(tmp.presets[0].plugin, "", instance_name);
476 tmp.presets[0].activate(plugins[nplugin]);
477 main_win->refresh_plugin(plugins[nplugin]);
478 for(dictionary::const_iterator i = automation.begin(); i != automation.end(); ++i)
479 plugins[nplugin]->configure(i->first.c_str(), i->second.c_str());
485 void host_session::save(session_save_iface *stream)
487 dictionary tmp;
488 string pstr;
489 string i_name = stripfmt(client.input_name);
490 string o_name = stripfmt(client.output_name);
491 string m_name = stripfmt(client.midi_name);
492 tmp["input_prefix"] = i_name;
493 tmp["output_prefix"] = stripfmt(client.output_name);
494 tmp["midi_prefix"] = stripfmt(client.midi_name);
495 pstr = encode_map(tmp);
496 stream->write_next_item("global", pstr);
498 for (unsigned int i = 0; i < plugins.size(); i++) {
499 jack_host *p = plugins[i];
500 char ss[32];
501 plugin_preset preset;
502 preset.plugin = p->metadata->get_id();
503 preset.get_from(p);
504 snprintf(ss, sizeof(ss), "Plugin%d", i);
505 pstr = preset.to_xml();
506 tmp.clear();
507 tmp["instance_name"] = p->instance_name;
508 if (p->metadata->get_input_count())
509 tmp["input_name"] = p->get_inputs()[0].name.substr(i_name.length());
510 if (p->metadata->get_output_count())
511 tmp["output_name"] = p->get_outputs()[0].name.substr(o_name.length());
512 if (p->get_midi_port())
513 tmp["midi_name"] = p->get_midi_port()->name.substr(m_name.length());
514 tmp["preset"] = pstr;
515 dictionary automation;
516 gather_automation_params gap(automation);
517 p->send_automation_configures(&gap);
518 tmp["automation"] = encode_map(automation);
520 pstr = encode_map(tmp);
521 stream->write_next_item(ss, pstr);
525 void host_session::signal_handler(int signum)
527 switch (signum)
529 case SIGUSR1:
530 instance->save_file_on_next_idle_call = true;
531 break;
532 case SIGTERM:
533 case SIGHUP:
534 instance->quit_on_next_idle_call = signum;
535 break;
539 void host_session::on_idle()
541 if (save_file_on_next_idle_call)
543 save_file_on_next_idle_call = false;
544 main_win->save_file();
545 printf("LADISH Level 1 support: file '%s' saved\n", get_current_filename().c_str());
548 if (handle_event_on_next_idle_call)
550 jack_session_event_t *ev = handle_event_on_next_idle_call;
551 handle_event_on_next_idle_call = NULL;
552 handle_jack_session_event(ev);
554 if (quit_on_next_idle_call > 0)
556 printf("Quit requested through signal %d\n", quit_on_next_idle_call);
557 quit_on_next_idle_call = -quit_on_next_idle_call; // mark the event as handled but preserve signal number
558 session_env->quit_gui_loop();
562 void host_session::set_signal_handlers()
564 instance = this;
566 struct sigaction sa;
567 sa.sa_handler = signal_handler;
568 sigemptyset(&sa.sa_mask);
569 sa.sa_flags = SA_RESTART;
570 sigaction(SIGTERM, &sa, NULL);
571 sigaction(SIGHUP, &sa, NULL);
572 sigaction(SIGUSR1, &sa, NULL);
575 void host_session::reorder_plugins()
577 vector<int> order;
578 client.calculate_plugin_order(order);
579 client.apply_plugin_order(order);
582 std::string host_session::get_client_name() const
584 return client.name;
587 std::string host_session::get_current_filename() const
589 return current_filename;
592 void host_session::set_current_filename(const std::string &name)
594 current_filename = name;
597 host_session::~host_session()
599 delete main_win;