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)
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
22 #include <calf/giface.h>
23 #include <calf/host_session.h>
25 #include <calf/preset.h>
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";
42 autoconnect_midi_index
= -1;
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
))
58 for (int i
= 2; ; i
++)
60 string tmp
= string(effect_name
) + " (" + i2s(i
) + ")";
61 if (!instances
.count(tmp
))
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
);
75 #define PER_MODULE_ITEM(name, isSynth, jackname) jackname ", "
76 #include <calf/modulelist.h>
79 s
= s
.substr(0, s
.length() - 2);
80 throw text_exception("Unknown plugin name; allowed are: " + s
);
82 instances
.insert(jh
->instance_name
);
85 plugins
.push_back(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();
128 void host_session::session_callback(jack_session_event_t
*event
, void *arg
)
130 printf("session callback type %d\n", (int)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
)
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
;
149 save_file(fn
.c_str());
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
);
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
);
171 instances
.insert(jh
->instance_name
);
174 plugins
.push_back(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
);
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
);
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();
224 void host_session::connect()
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
)) {
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());
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
);
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());
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());
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
);
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
);
292 if (!load_name
.empty())
294 char *error
= open_file(load_name
.c_str());
297 bool suppress_error
= false;
298 if (only_load_if_exists
)
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
308 main_win
->show_error("Cannot load '" + load_name
+ "': " + error
);
314 set_current_filename(load_name
);
317 session_manager
->connect("calf-" + client_name
);
320 void host_session::close()
323 session_manager
->disconnect();
324 main_win
->on_closed();
326 client
.delete_plugins();
327 client
.destroy_automation_input();
331 static string
stripfmt(string x
)
335 if (x
.substr(x
.length() - 2) != "%d")
337 return x
.substr(0, x
.length() - 2);
340 char *host_session::open_file(const char *name
)
344 remove_all_plugins();
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());
377 struct gather_automation_params
: public send_configure_iface
381 gather_automation_params(stringstream
&_xml
)
385 gather_automation_params(dictionary
&_dict
)
390 virtual void send_configure(const char *key
, const char *value
)
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
);
405 data
<< "<?xml version=\"1.1\" encoding=\"utf-8\"?>" << endl
;
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();
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()));
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
))
435 return strdup(strerror(e
));
438 return strdup(strerror(errno
));
443 void host_session::load(session_load_iface
*stream
)
445 // printf("!!!Restore data set!!!\n");
446 remove_all_plugins();
448 while(stream
->get_next_item(key
, data
)) {
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());
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
)
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
];
501 plugin_preset preset
;
502 preset
.plugin
= p
->metadata
->get_id();
504 snprintf(ss
, sizeof(ss
), "Plugin%d", i
);
505 pstr
= preset
.to_xml();
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
)
530 instance
->save_file_on_next_idle_call
= true;
534 instance
->quit_on_next_idle_call
= signum
;
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()
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()
578 client
.calculate_plugin_order(order
);
579 client
.apply_plugin_order(order
);
582 std::string
host_session::get_client_name() const
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()