1 /* Calf DSP Library Utility Application - calfjackhost
2 * A class that contains a JACK host session
3 *
4 * Copyright (C) 2007-2011 Krzysztof Foltman
5 *
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.
10 *
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.
15 *
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.
20 */
21
22 #include <stdlib.h>
23 #include <calf/giface.h>
24 #include <calf/host_session.h>
25 #include <calf/gui.h>
26 #include <calf/preset.h>
27 #include <getopt.h>
28 #include <sys/stat.h>
29
30 using namespace std;
31 using namespace calf_utils;
32 using namespace calf_plugins;
33
34 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
35
36 host_session *host_session::instance = NULL;
37
host_session(session_environment_iface * se)38 host_session::host_session(session_environment_iface *se)
39 {
40 client_name = "Calf Studio Gear";
41 calfjackhost_cmd = "calfjackhost";
42 session_env = se;
43 autoconnect_midi_index = -1;
44 gui_win = NULL;
45 has_gui = true;
46 has_trayicon = true;
47 session_manager = NULL;
48 only_load_if_exists = false;
49 save_file_on_next_idle_call = false;
50 quit_on_next_idle_call = 0;
51 handle_event_on_next_idle_call = NULL;
52 }
53
54 extern "C" plugin_metadata_iface *create_calf_metadata_by_name(const char *effect_name);
55
get_full_plugin_name(const std::string & effect_name)56 std::string host_session::get_full_plugin_name(const std::string &effect_name)
57 {
58 plugin_metadata_iface *metadata = create_calf_metadata_by_name(effect_name.c_str());
59 if (!metadata)
60 return effect_name;
61
62 string full_name = metadata->get_label();
63 delete metadata;
64 return full_name;
65 }
66
get_next_instance_name(const std::string & instance_name)67 std::string host_session::get_next_instance_name(const std::string &instance_name)
68 {
69 if (!instances.count(instance_name))
70 return instance_name;
71
72 for (int i = 2; ; i++)
73 {
74 string tmp = instance_name + " (" + i2s(i) + ")";
75 if (!instances.count(tmp))
76 return tmp;
77 }
78 assert(0);
79 return "-";
80 }
81
add_plugin(string name,string preset,string instance_name)82 void host_session::add_plugin(string name, string preset, string instance_name)
83 {
84 if (instance_name.empty())
85 instance_name = get_next_instance_name(get_full_plugin_name(name));
86 jack_host *jh = create_jack_host(&client, name.c_str(), instance_name, main_win);
87 if (!jh) {
88 string s =
89 #define PER_MODULE_ITEM(name, isSynth, jackname) jackname ", "
90 #include <calf/modulelist.h>
91 ;
92 if (!s.empty())
93 s = s.substr(0, s.length() - 2);
94 throw text_exception("Unknown plugin name \"" + name + "\" - allowed are: " + s);
95 }
96 instances.insert(jh->instance_name);
97 jh->create();
98
99 plugins.push_back(jh);
100 client.add(jh);
101 if (has_gui)
102 main_win->add_plugin(jh);
103 if (!preset.empty()) {
104 if (!activate_preset(plugins.size() - 1, preset, false))
105 {
106 if (!activate_preset(plugins.size() - 1, preset, true))
107 {
108 fprintf(stderr, "Unknown preset: %s\n", preset.c_str());
109 }
110 }
111 }
112 }
113
create_plugins_from_list()114 void host_session::create_plugins_from_list()
115 {
116 for (unsigned int i = 0; i < plugin_names.size(); i++) {
117 add_plugin(plugin_names[i], presets.count(i) ? presets[i] : string());
118 }
119 }
120
on_main_window_destroy()121 void host_session::on_main_window_destroy()
122 {
123 session_env->quit_gui_loop();
124 }
125
open()126 void host_session::open()
127 {
128 if (!input_name.empty()) client.input_name = input_name;
129 if (!output_name.empty()) client.output_name = output_name;
130 if (!midi_name.empty()) client.midi_name = midi_name;
131
132 client.open(client_name.c_str(), !jack_session_id.empty() ? jack_session_id.c_str() : NULL);
133 jack_set_session_callback(client.client, session_callback, this);
134
135 if (has_gui) {
136 main_win = session_env->create_main_window();
137 main_win->set_owner(this);
138 main_win->add_condition("jackhost");
139 main_win->add_condition("directlink");
140 main_win->add_condition("configure");
141 }
142 client.create_automation_input();
143 if (!session_manager || !session_manager->is_being_restored())
144 create_plugins_from_list();
145 if (has_gui)
146 main_win->create();
147 if (has_trayicon)
148 main_win->create_status_icon();
149 }
150
session_callback(jack_session_event_t * event,void * arg)151 void host_session::session_callback(jack_session_event_t *event, void *arg)
152 {
153 printf("session callback type %d\n", (int)event->type);
154 switch(event->type)
155 {
156 case JackSessionSave:
157 case JackSessionSaveAndQuit:
158 case JackSessionSaveTemplate:
159 host_session *hs = (host_session *)arg;
160 if(hs->has_gui){
161 hs->handle_jack_session_event(event);
162 } else {
163 hs->handle_event_on_next_idle_call = event;
164 }
165 }
166 // XXXKF if more than one event happen in a short sequence, the other event
167 // may be lost. This calls for implementing a proper event queue.
168 }
169
handle_jack_session_event(jack_session_event_t * event)170 void host_session::handle_jack_session_event(jack_session_event_t *event)
171 {
172 try {
173 int size = asprintf(&event->command_line, "%s --load ${SESSION_DIR}" G_DIR_SEPARATOR_S "rack.xml --session-id %s" , calfjackhost_cmd.c_str(), event->client_uuid);
174 if (size > -1) {
175 string fn = event->session_dir;
176 fn += "rack.xml";
177 save_file(fn.c_str());
178 }
179 }
180 catch(...)
181 {
182 event->flags = JackSessionSaveError;
183 // let the server know that the save operation failed
184 jack_session_reply(client.client, event);
185 jack_session_event_free(event);
186 throw;
187 }
188
189 if (event->type == JackSessionSaveAndQuit)
190 quit_on_next_idle_call = 1;
191 jack_session_reply(client.client, event);
192 jack_session_event_free(event);
193 }
194
new_plugin(const char * name)195 void host_session::new_plugin(const char *name)
196 {
197 jack_host *jh = create_jack_host(&client, name, get_next_instance_name(get_full_plugin_name(name)), main_win);
198 if (!jh)
199 return;
200 instances.insert(jh->instance_name);
201 jh->create();
202
203 plugins.push_back(jh);
204 client.add(jh);
205 if (has_gui)
206 main_win->add_plugin(jh);
207 }
208
remove_plugin(plugin_ctl_iface * plugin)209 void host_session::remove_plugin(plugin_ctl_iface *plugin)
210 {
211 for (unsigned int i = 0; i < plugins.size(); i++)
212 {
213 if (plugins[i] == plugin)
214 {
215 instances.erase(plugins[i]->instance_name);
216 client.del(plugins[i]);
217 plugins.erase(plugins.begin() + i);
218 if (has_gui)
219 main_win->del_plugin(plugin);
220 delete plugin;
221 return;
222 }
223 }
224 }
225
rename_plugin(plugin_ctl_iface * plugin,const char * name)226 void host_session::rename_plugin(plugin_ctl_iface *plugin, const char *name) {
227 std::string name_ = get_next_instance_name(name);
228 for (unsigned int i = 0; i < plugins.size(); i++) {
229 if (plugins[i] == plugin) {
230 instances.erase(plugins[i]->instance_name);
231 instances.insert(name_);
232 plugins[i]->rename(name_);
233 if (has_gui)
234 main_win->rename_plugin(plugin, name_);
235 return;
236 }
237 }
238 }
239
remove_all_plugins()240 void host_session::remove_all_plugins()
241 {
242 while(!plugins.empty())
243 {
244 jack_host *plugin = plugins[0];
245 client.del(plugins[0]);
246 plugins.erase(plugins.begin());
247 if (has_gui)
248 main_win->del_plugin(plugin);
249 delete plugin;
250 }
251 instances.clear();
252 }
253
activate_preset(int plugin_no,const std::string & preset,bool builtin)254 bool host_session::activate_preset(int plugin_no, const std::string &preset, bool builtin)
255 {
256 string cur_plugin = plugins[plugin_no]->metadata->get_id();
257 preset_vector &pvec = (builtin ? get_builtin_presets() : get_user_presets()).presets;
258 for (unsigned int i = 0; i < pvec.size(); i++) {
259 if (pvec[i].name == preset && pvec[i].plugin == cur_plugin)
260 {
261 pvec[i].activate(plugins[plugin_no]);
262 if (gui_win)
263 gui_win->refresh();
264 return true;
265 }
266 }
267 return false;
268 }
269
connect()270 void host_session::connect()
271 {
272 client.activate();
273 if (session_manager)
274 session_manager->set_jack_client_name(client.get_name());
275 if ((!session_manager || !session_manager->is_being_restored()) && load_name.empty())
276 {
277 string cnp = client.get_name() + ":";
278 for (unsigned int i = 0; i < plugins.size(); i++) {
279 if (chains.count(i)) {
280 if (!i)
281 {
282 if (plugins[0]->metadata->get_input_count() < 2)
283 {
284 fprintf(stderr, "Cannot connect input to plugin %s - the plugin no input ports\n", plugins[0]->name.c_str());
285 } else {
286 client.connect(getenv("CALF_IN_1")?getenv("CALF_IN_1"):"system:capture_1", cnp + plugins[0]->get_inputs()[0].name);
287 client.connect(getenv("CALF_IN_2")?getenv("CALF_IN_2"):"system:capture_2", cnp + plugins[0]->get_inputs()[1].name);
288 }
289 }
290 else
291 {
292 if (plugins[i - 1]->metadata->get_output_count() < 2 || plugins[i]->metadata->get_input_count() < 2)
293 {
294 fprintf(stderr, "Cannot connect plugins %s and %s - incompatible ports\n", plugins[i - 1]->name.c_str(), plugins[i]->name.c_str());
295 }
296 else {
297 client.connect(cnp + plugins[i - 1]->get_outputs()[0].name, cnp + plugins[i]->get_inputs()[0].name);
298 client.connect(cnp + plugins[i - 1]->get_outputs()[1].name, cnp + plugins[i]->get_inputs()[1].name);
299 }
300 }
301 }
302 }
303 if (chains.count(plugins.size()) && plugins.size())
304 {
305 int last = plugins.size() - 1;
306 if (plugins[last]->metadata->get_output_count() < 2)
307 {
308 fprintf(stderr, "Cannot connect plugin %s to output - incompatible ports\n", plugins[last]->name.c_str());
309 } else {
310 client.connect(cnp + plugins[last]->get_outputs()[0].name, getenv("CALF_OUT_1")?getenv("CALF_OUT_1"):"system:playback_1");
311 client.connect(cnp + plugins[last]->get_outputs()[1].name, getenv("CALF_OUT_2")?getenv("CALF_OUT_2"):"system:playback_2");
312 }
313 }
314 if (autoconnect_midi != "") {
315 for (unsigned int i = 0; i < plugins.size(); i++)
316 {
317 if (plugins[i]->metadata->get_midi())
318 client.connect(autoconnect_midi, cnp + plugins[i]->get_midi_port()->name);
319 }
320 }
321 else
322 if (autoconnect_midi_index != -1) {
323 const char **ports = client.get_ports(".*:.*", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput | JackPortIsPhysical);
324 for (int j = 0; ports && ports[j]; j++)
325 {
326 if (j + 1 == autoconnect_midi_index) {
327 for (unsigned int i = 0; i < plugins.size(); i++)
328 {
329 if (plugins[i]->metadata->get_midi())
330 client.connect(ports[j], cnp + plugins[i]->get_midi_port()->name);
331 }
332 break;
333 }
334 }
335 free(ports);
336 }
337 }
338 if (!load_name.empty())
339 {
340 char *error = open_file(load_name.c_str());
341 if (error)
342 {
343 bool suppress_error = false;
344 if (only_load_if_exists)
345 {
346 struct stat s;
347 int stat_result = stat(load_name.c_str(), &s);
348 if (stat_result == -1 && errno == ENOENT)
349 suppress_error = true;
350 }
351 // If the file is optional and it didn't exist, suppress the error
352 if (!suppress_error)
353 {
354 if (has_gui)
355 main_win->show_error("Cannot load '" + load_name + "': " + error);
356
357 load_name = "";
358 }
359 g_free(error);
360 }
361 set_current_filename(load_name);
362 }
363 if (session_manager)
364 session_manager->connect("calf-" + client_name);
365 }
366
close()367 void host_session::close()
368 {
369 if (session_manager)
370 session_manager->disconnect();
371 if (has_gui){
372 main_win->on_closed();
373 delete main_win;
374 }
375
376 client.deactivate();
377 client.delete_plugins();
378 client.destroy_automation_input();
379 client.close();
380 }
381
stripfmt(string x)382 static string stripfmt(string x)
383 {
384 if (x.length() < 2)
385 return x;
386 if (x.substr(x.length() - 2) != "%d")
387 return x;
388 return x.substr(0, x.length() - 2);
389 }
390
open_file(const char * name)391 char *host_session::open_file(const char *name)
392 {
393 preset_list pl;
394 try {
395 remove_all_plugins();
396 pl.load(name, true);
397 printf("Size %d\n", (int)pl.plugins.size());
398 for (unsigned int i = 0; i < pl.plugins.size(); i++)
399 {
400 preset_list::plugin_snapshot &ps = pl.plugins[i];
401 client.input_nr = ps.input_index;
402 client.output_nr = ps.output_index;
403 client.midi_nr = ps.midi_index;
404 printf("Loading %s\n", ps.type.c_str());
405 if (ps.preset_offset < (int)pl.presets.size())
406 {
407 add_plugin(ps.type, "", ps.instance_name);
408 pl.presets[ps.preset_offset].activate(plugins[i]);
409 for (size_t j = 0; j < ps.automation_entries.size(); ++j)
410 {
411 const pair<string, string> &p = ps.automation_entries[j];
412 plugins[i]->configure(p.first.c_str(), p.second.c_str());
413 }
414 if (has_gui)
415 main_win->refresh_plugin(plugins[i]);
416 }
417 }
418 }
419 catch(preset_exception &e)
420 {
421 // XXXKF this will leak
422 char *data = strdup(e.what());
423 return data;
424 }
425
426 return NULL;
427 }
428
429 struct gather_automation_params: public send_configure_iface
430 {
431 stringstream *xml;
432 dictionary *dict;
gather_automation_paramsgather_automation_params433 gather_automation_params(stringstream &_xml)
434 : xml(&_xml)
435 , dict(NULL)
436 {}
gather_automation_paramsgather_automation_params437 gather_automation_params(dictionary &_dict)
438 : xml(NULL)
439 , dict(&_dict)
440 {}
441
send_configuregather_automation_params442 virtual void send_configure(const char *key, const char *value)
443 {
444 if (xml)
445 *xml << "<automation key=\"" << key << "\" value=\"" << value << "\" />" << endl;
446 if (dict && key && value)
447 (*dict)[key] = value;
448 }
449 };
450
save_file(const char * name)451 char *host_session::save_file(const char *name)
452 {
453 string i_name = stripfmt(client.input_name);
454 string o_name = stripfmt(client.output_name);
455 string m_name = stripfmt(client.midi_name);
456 stringstream data;
457 data << "<?xml version=\"1.1\" encoding=\"utf-8\"?>" << endl;
458 data << "<rack>";
459 for (unsigned int i = 0; i < plugins.size(); i++) {
460 jack_host *p = plugins[i];
461 plugin_preset preset;
462 preset.plugin = p->metadata->get_id();
463 preset.get_from(p);
464 data << "<plugin";
465 data << to_xml_attr("type", preset.plugin);
466 data << to_xml_attr("instance-name", p->instance_name);
467 if (p->metadata->get_input_count())
468 data << to_xml_attr("input-index", p->get_inputs()[0].name.substr(i_name.length()));
469 if (p->metadata->get_output_count())
470 data << to_xml_attr("output-index", p->get_outputs()[0].name.substr(o_name.length()));
471 if (p->get_midi_port())
472 data << to_xml_attr("midi-index", p->get_midi_port()->name.substr(m_name.length()));
473 data << ">" << endl;
474 data << preset.to_xml();
475 gather_automation_params gap(data);
476 p->send_automation_configures(&gap);
477 data << "</plugin>" << endl;
478 }
479 data << "</rack>" << endl;
480 string datastr(data.str());
481 FILE *f = fopen(name, "w");
482 if (!f || 1 != fwrite(datastr.c_str(), datastr.length(), 1, f))
483 {
484 int e = errno;
485 if (f)
486 fclose(f);
487 return strdup(strerror(e));
488 }
489 if (fclose(f))
490 return strdup(strerror(errno));
491
492 return NULL;
493 }
494
load(session_load_iface * stream)495 void host_session::load(session_load_iface *stream)
496 {
497 // printf("!!!Restore data set!!!\n");
498 remove_all_plugins();
499 string key, data;
500 while(stream->get_next_item(key, data)) {
501 if (key == "global")
502 {
503 dictionary dict;
504 decode_map(dict, data);
505 if (dict.count("input_prefix")) client.input_name = dict["input_prefix"]+"%d";
506 if (dict.count("output_prefix")) client.output_name = dict["output_prefix"]+"%d";
507 if (dict.count("midi_prefix")) client.midi_name = dict["midi_prefix"]+"%d";
508 }
509 if (!strncmp(key.c_str(), "Plugin", 6))
510 {
511 unsigned int nplugin = atoi(key.c_str() + 6);
512 dictionary dict, automation;
513 decode_map(dict, data);
514 data = dict["preset"];
515 if (dict.count("automation"))
516 decode_map(automation, dict["automation"]);
517 string instance_name;
518 if (dict.count("instance_name")) instance_name = dict["instance_name"];
519 if (dict.count("input_name")) client.input_nr = atoi(dict["input_name"].c_str());
520 if (dict.count("output_name")) client.output_nr = atoi(dict["output_name"].c_str());
521 if (dict.count("midi_name")) client.midi_nr = atoi(dict["midi_name"].c_str());
522 preset_list tmp;
523 tmp.parse("<presets>"+data+"</presets>", false);
524 if (tmp.presets.size())
525 {
526 printf("Load plugin %s\n", tmp.presets[0].plugin.c_str());
527 add_plugin(tmp.presets[0].plugin, "", instance_name);
528 tmp.presets[0].activate(plugins[nplugin]);
529 if (has_gui)
530 main_win->refresh_plugin(plugins[nplugin]);
531 for(dictionary::const_iterator i = automation.begin(); i != automation.end(); ++i)
532 plugins[nplugin]->configure(i->first.c_str(), i->second.c_str());
533 }
534 }
535 }
536 }
537
save(session_save_iface * stream)538 void host_session::save(session_save_iface *stream)
539 {
540 dictionary tmp;
541 string pstr;
542 string i_name = stripfmt(client.input_name);
543 string o_name = stripfmt(client.output_name);
544 string m_name = stripfmt(client.midi_name);
545 tmp["input_prefix"] = i_name;
546 tmp["output_prefix"] = stripfmt(client.output_name);
547 tmp["midi_prefix"] = stripfmt(client.midi_name);
548 pstr = encode_map(tmp);
549 stream->write_next_item("global", pstr);
550
551 for (unsigned int i = 0; i < plugins.size(); i++) {
552 jack_host *p = plugins[i];
553 char ss[32];
554 plugin_preset preset;
555 preset.plugin = p->metadata->get_id();
556 preset.get_from(p);
557 snprintf(ss, sizeof(ss), "Plugin%d", i);
558 pstr = preset.to_xml();
559 tmp.clear();
560 tmp["instance_name"] = p->instance_name;
561 if (p->metadata->get_input_count())
562 tmp["input_name"] = p->get_inputs()[0].name.substr(i_name.length());
563 if (p->metadata->get_output_count())
564 tmp["output_name"] = p->get_outputs()[0].name.substr(o_name.length());
565 if (p->get_midi_port())
566 tmp["midi_name"] = p->get_midi_port()->name.substr(m_name.length());
567 tmp["preset"] = pstr;
568 dictionary automation;
569 gather_automation_params gap(automation);
570 p->send_automation_configures(&gap);
571 tmp["automation"] = encode_map(automation);
572
573 pstr = encode_map(tmp);
574 stream->write_next_item(ss, pstr);
575 }
576 }
577
signal_handler(int signum)578 void host_session::signal_handler(int signum)
579 {
580 switch (signum)
581 {
582 case SIGUSR1:
583 instance->save_file_on_next_idle_call = true;
584 break;
585 case SIGTERM:
586 case SIGHUP:
587 instance->quit_on_next_idle_call = signum;
588 break;
589 }
590 }
591
on_idle()592 void host_session::on_idle()
593 {
594 if (save_file_on_next_idle_call)
595 {
596 save_file_on_next_idle_call = false;
597 main_win->save_file();
598 printf("LADISH Level 1 support: file '%s' saved\n", get_current_filename().c_str());
599 }
600
601 if (handle_event_on_next_idle_call)
602 {
603 jack_session_event_t *ev = handle_event_on_next_idle_call;
604 handle_event_on_next_idle_call = NULL;
605 handle_jack_session_event(ev);
606 }
607 if (quit_on_next_idle_call > 0)
608 {
609 printf("Quit requested through signal %d\n", quit_on_next_idle_call);
610 quit_on_next_idle_call = -quit_on_next_idle_call; // mark the event as handled but preserve signal number
611 session_env->quit_gui_loop();
612 }
613 }
614
set_signal_handlers()615 void host_session::set_signal_handlers()
616 {
617 instance = this;
618
619 struct sigaction sa;
620 sa.sa_handler = signal_handler;
621 sigemptyset(&sa.sa_mask);
622 sa.sa_flags = SA_RESTART;
623 sigaction(SIGTERM, &sa, NULL);
624 sigaction(SIGHUP, &sa, NULL);
625 sigaction(SIGUSR1, &sa, NULL);
626 }
627
reorder_plugins()628 void host_session::reorder_plugins()
629 {
630 vector<int> order;
631 client.calculate_plugin_order(order);
632 client.apply_plugin_order(order);
633 }
634
get_client_name() const635 std::string host_session::get_client_name() const
636 {
637 return client.name;
638 }
639
get_current_filename() const640 std::string host_session::get_current_filename() const
641 {
642 return current_filename;
643 }
644
set_current_filename(const std::string & name)645 void host_session::set_current_filename(const std::string &name)
646 {
647 current_filename = name;
648 }
649
~host_session()650 host_session::~host_session()
651 {
652 }
653