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