1 /* Calf DSP Library Utility Application - calfjackhost
2 * Standalone application module wrapper example.
4 * Copyright (C) 2007 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
26 #include <glade/glade.h>
27 #include <jack/jack.h>
29 #include <lash/lash.h>
31 #include <calf/giface.h>
32 #include <calf/jackhost.h>
33 #include <calf/modules.h>
34 #include <calf/modules_dev.h>
36 #include <calf/preset.h>
37 #include <calf/preset_gui.h>
38 #include <calf/main_win.h>
39 #include <calf/utils.h>
42 using namespace calf_utils
;
43 using namespace calf_plugins
;
45 // I don't need anyone to tell me this is stupid. I already know that :)
46 plugin_gui_window
*gui_win
;
48 const char *client_name
= "calfhost";
50 jack_host_base
*calf_plugins::create_jack_host(const char *effect_name
, const std::string
&instance_name
, calf_plugins::progress_report_iface
*priface
)
52 #define PER_MODULE_ITEM(name, isSynth, jackname) if (!strcasecmp(effect_name, jackname)) return new jack_host<name##_audio_module>(effect_name, instance_name, priface);
53 #include <calf/modulelist.h>
57 void jack_host_base::open(jack_client
*_client
)
59 client
= _client
; //jack_client_open(client_name, JackNullOption, &status);
69 void jack_host_base::create_ports() {
72 string prefix
= client
->name
+ ":";
73 static const char *suffixes
[] = { "l", "r", "2l", "2r" };
74 port
*inputs
= get_inputs();
75 port
*outputs
= get_outputs();
76 int in_count
= get_input_count(), out_count
= get_output_count();
77 for (int i
=0; i
<in_count
; i
++) {
78 sprintf(buf
, "%s_in_%s", instance_name
.c_str(), suffixes
[i
]);
79 sprintf(buf2
, client
->input_name
.c_str(), client
->input_nr
++);
80 inputs
[i
].name
= buf2
;
81 inputs
[i
].handle
= jack_port_register(client
->client
, buf
, JACK_DEFAULT_AUDIO_TYPE
, JackPortIsInput
, 0);
82 inputs
[i
].data
= NULL
;
83 if (!inputs
[i
].handle
)
84 throw text_exception("Could not create JACK input port");
85 jack_port_set_alias(inputs
[i
].handle
, (prefix
+ buf2
).c_str());
88 sprintf(buf
, "%s_midi_in", instance_name
.c_str());
89 sprintf(buf2
, client
->midi_name
.c_str(), client
->midi_nr
++);
90 midi_port
.name
= buf2
;
91 midi_port
.handle
= jack_port_register(client
->client
, buf
, JACK_DEFAULT_MIDI_TYPE
, JackPortIsInput
, 0);
92 if (!midi_port
.handle
)
93 throw text_exception("Could not create JACK MIDI port");
94 jack_port_set_alias(midi_port
.handle
, (prefix
+ buf2
).c_str());
96 for (int i
=0; i
<out_count
; i
++) {
97 sprintf(buf
, "%s_out_%s", instance_name
.c_str(), suffixes
[i
]);
98 sprintf(buf2
, client
->output_name
.c_str(), client
->output_nr
++);
99 outputs
[i
].name
= buf2
;
100 outputs
[i
].handle
= jack_port_register(client
->client
, buf
, JACK_DEFAULT_AUDIO_TYPE
, JackPortIsOutput
, 0);
101 outputs
[i
].data
= NULL
;
102 if (!outputs
[i
].handle
)
103 throw text_exception("Could not create JACK output port");
104 jack_port_set_alias(outputs
[i
].handle
, (prefix
+ buf2
).c_str());
108 void jack_host_base::close() {
109 port
*inputs
= get_inputs(), *outputs
= get_outputs();
110 int input_count
= get_input_count(), output_count
= get_output_count();
111 for (int i
= 0; i
< input_count
; i
++) {
112 jack_port_unregister(client
->client
, inputs
[i
].handle
);
113 inputs
[i
].data
= NULL
;
115 for (int i
= 0; i
< output_count
; i
++) {
116 jack_port_unregister(client
->client
, outputs
[i
].handle
);
117 outputs
[i
].data
= NULL
;
120 jack_port_unregister(client
->client
, midi_port
.handle
);
124 void destroy(GtkWindow
*window
, gpointer data
)
129 void gui_win_destroy(GtkWindow
*window
, gpointer data
)
133 static struct option long_options
[] = {
135 {"version", 0, 0, 'v'},
136 {"client", 1, 0, 'c'},
137 {"effect", 0, 0, 'e'},
138 {"input", 1, 0, 'i'},
139 {"output", 1, 0, 'o'},
140 {"connect-midi", 1, 0, 'M'},
144 void print_help(char *argv
[])
146 printf("JACK host for Calf effects\n"
147 "Syntax: %s [--client <name>] [--input <name>] [--output <name>] [--midi <name>]\n"
148 " [--connect-midi <name|capture-index>] [--help] [--version] [!] pluginname[:<preset>] [!] ...\n",
152 int jack_client::do_jack_process(jack_nframes_t nframes
, void *p
)
154 jack_client
*self
= (jack_client
*)p
;
155 ptlock
lock(self
->mutex
);
156 for(unsigned int i
= 0; i
< self
->plugins
.size(); i
++)
157 self
->plugins
[i
]->process(nframes
);
161 int jack_client::do_jack_bufsize(jack_nframes_t numsamples
, void *p
)
163 jack_client
*self
= (jack_client
*)p
;
164 ptlock
lock(self
->mutex
);
165 for(unsigned int i
= 0; i
< self
->plugins
.size(); i
++)
166 self
->plugins
[i
]->cache_ports();
170 void jack_client::delete_plugins()
173 for (unsigned int i
= 0; i
< plugins
.size(); i
++) {
174 // plugins[i]->close();
180 struct host_session
: public main_window_owner_iface
, public calf_plugins::progress_report_iface
182 string client_name
, input_name
, output_name
, midi_name
;
183 vector
<string
> plugin_names
;
184 map
<int, string
> presets
;
186 lash_client_t
*lash_client
;
187 lash_args_t
*lash_args
;
190 // these are not saved
192 string autoconnect_midi
;
194 vector
<jack_host_base
*> plugins
;
195 main_window
*main_win
;
197 bool restoring_session
;
198 std::set
<std::string
> instances
;
199 GtkWidget
*progress_window
;
203 void add_plugin(string name
, string preset
, string instance_name
= string());
204 void create_plugins_from_list();
207 static gboolean
update_lash(void *self
) { ((host_session
*)self
)->update_lash(); return TRUE
; }
209 bool activate_preset(int plugin
, const std::string
&preset
, bool builtin
);
211 void send_lash(LASH_Event_Type type
, const std::string
&data
) {
212 lash_send_event(lash_client
, lash_event_new_with_all(type
, data
.c_str()));
214 void send_config(const char *key
, const std::string
&data
) {
215 char *buffer
= new char[data
.length()];
216 memcpy(buffer
, data
.data(), data
.length());
217 lash_config_t
*cfg
= lash_config_new_with_key(strdup(key
));
218 lash_config_set_value(cfg
, buffer
, data
.length());
219 lash_send_config(lash_client
, cfg
);
222 virtual void new_plugin(const char *name
);
223 virtual void remove_plugin(plugin_ctl_iface
*plugin
);
224 void remove_all_plugins();
225 std::string
get_next_instance_name(const std::string
&effect_name
);
227 /// Create a toplevel window with progress bar
228 GtkWidget
*create_progress_window();
229 /// Implementation of progress_report_iface function
230 void report_progress(float percentage
, const std::string
&message
);
233 host_session::host_session()
235 client_name
= "calf";
241 restoring_session
= false;
242 main_win
= new main_window
;
243 main_win
->set_owner(this);
244 progress_window
= NULL
;
247 std::string
host_session::get_next_instance_name(const std::string
&effect_name
)
249 if (!instances
.count(effect_name
))
251 for (int i
= 2; ; i
++)
253 string tmp
= string(effect_name
) + i2s(i
);
254 if (!instances
.count(tmp
))
261 void host_session::add_plugin(string name
, string preset
, string instance_name
)
263 if (instance_name
.empty())
264 instance_name
= get_next_instance_name(name
);
265 jack_host_base
*jh
= create_jack_host(name
.c_str(), instance_name
, this);
267 #ifdef ENABLE_EXPERIMENTAL
271 #define PER_MODULE_ITEM(name, isSynth, jackname) jackname ", "
272 #include <calf/modulelist.h>
275 s
= s
.substr(0, s
.length() - 2);
276 throw text_exception("Unknown plugin name; allowed are: " + s
);
278 instances
.insert(jh
->instance_name
);
281 plugins
.push_back(jh
);
283 main_win
->add_plugin(jh
);
284 if (!preset
.empty()) {
285 if (!activate_preset(plugins
.size() - 1, preset
, false))
287 if (!activate_preset(plugins
.size() - 1, preset
, true))
289 fprintf(stderr
, "Unknown preset: %s\n", preset
.c_str());
295 void host_session::report_progress(float percentage
, const std::string
&message
)
297 if (percentage
< 100)
299 if (!progress_window
) {
300 progress_window
= create_progress_window();
301 gtk_window_set_modal (GTK_WINDOW (progress_window
), TRUE
);
302 if (main_win
->toplevel
)
303 gtk_window_set_transient_for (GTK_WINDOW (progress_window
), main_win
->toplevel
);
305 gtk_widget_show(progress_window
);
306 GtkWidget
*pbar
= gtk_bin_get_child (GTK_BIN (progress_window
));
307 if (!message
.empty())
308 gtk_progress_bar_set_text (GTK_PROGRESS_BAR (pbar
), message
.c_str());
309 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (pbar
), percentage
/ 100.0);
313 if (progress_window
) {
314 gtk_window_set_modal (GTK_WINDOW (progress_window
), FALSE
);
315 gtk_widget_destroy (progress_window
);
316 progress_window
= NULL
;
320 while (gtk_events_pending ())
321 gtk_main_iteration ();
325 void host_session::create_plugins_from_list()
327 for (unsigned int i
= 0; i
< plugin_names
.size(); i
++) {
328 add_plugin(plugin_names
[i
], presets
.count(i
) ? presets
[i
] : string());
332 GtkWidget
*host_session::create_progress_window()
334 GtkWidget
*tlw
= gtk_window_new ( GTK_WINDOW_TOPLEVEL
);
335 gtk_window_set_type_hint (GTK_WINDOW (tlw
), GDK_WINDOW_TYPE_HINT_DIALOG
);
336 GtkWidget
*pbar
= gtk_progress_bar_new();
337 gtk_container_add (GTK_CONTAINER(tlw
), pbar
);
338 gtk_widget_show_all (pbar
);
342 void host_session::open()
344 if (!input_name
.empty()) client
.input_name
= input_name
;
345 if (!output_name
.empty()) client
.output_name
= output_name
;
346 if (!midi_name
.empty()) client
.midi_name
= midi_name
;
347 client
.open(client_name
.c_str());
348 main_win
->prefix
= client_name
+ " - ";
349 main_win
->conditions
.insert("jackhost");
350 main_win
->conditions
.insert("directlink");
351 if (!restoring_session
)
352 create_plugins_from_list();
354 gtk_signal_connect(GTK_OBJECT(main_win
->toplevel
), "destroy", G_CALLBACK(destroy
), NULL
);
357 void host_session::new_plugin(const char *name
)
359 jack_host_base
*jh
= create_jack_host(name
, get_next_instance_name(name
), this);
362 instances
.insert(jh
->instance_name
);
365 plugins
.push_back(jh
);
367 main_win
->add_plugin(jh
);
370 void host_session::remove_plugin(plugin_ctl_iface
*plugin
)
372 for (unsigned int i
= 0; i
< plugins
.size(); i
++)
374 if (plugins
[i
] == plugin
)
377 plugins
.erase(plugins
.begin() + i
);
378 main_win
->del_plugin(plugin
);
385 void host_session::remove_all_plugins()
387 while(!plugins
.empty())
389 plugin_ctl_iface
*plugin
= plugins
[0];
391 plugins
.erase(plugins
.begin());
392 main_win
->del_plugin(plugin
);
397 bool host_session::activate_preset(int plugin_no
, const std::string
&preset
, bool builtin
)
399 string cur_plugin
= plugins
[plugin_no
]->get_id();
400 preset_vector
&pvec
= (builtin
? get_builtin_presets() : get_user_presets()).presets
;
401 for (unsigned int i
= 0; i
< pvec
.size(); i
++) {
402 if (pvec
[i
].name
== preset
&& pvec
[i
].plugin
== cur_plugin
)
404 pvec
[i
].activate(plugins
[plugin_no
]);
405 if (gui_win
&& gui_win
->gui
)
406 gui_win
->gui
->refresh();
413 void host_session::connect()
418 lash_jack_client_name(lash_client
, client
.get_name().c_str());
420 if (!restoring_session
)
422 string cnp
= client
.get_name() + ":";
423 for (unsigned int i
= 0; i
< plugins
.size(); i
++) {
424 if (chains
.count(i
)) {
427 if (plugins
[0]->get_output_count() < 2)
429 fprintf(stderr
, "Cannot connect input to plugin %s - incompatible ports\n", plugins
[0]->name
.c_str());
431 client
.connect("system:capture_1", cnp
+ plugins
[0]->get_inputs()[0].name
);
432 client
.connect("system:capture_2", cnp
+ plugins
[0]->get_inputs()[1].name
);
437 if (plugins
[i
- 1]->get_output_count() < 2 || plugins
[i
]->get_input_count() < 2)
439 fprintf(stderr
, "Cannot connect plugins %s and %s - incompatible ports\n", plugins
[i
- 1]->name
.c_str(), plugins
[i
]->name
.c_str());
442 client
.connect(cnp
+ plugins
[i
- 1]->get_outputs()[0].name
, cnp
+ plugins
[i
]->get_inputs()[0].name
);
443 client
.connect(cnp
+ plugins
[i
- 1]->get_outputs()[1].name
, cnp
+ plugins
[i
]->get_inputs()[1].name
);
448 if (chains
.count(plugins
.size()) && plugins
.size())
450 int last
= plugins
.size() - 1;
451 if (plugins
[last
]->get_output_count() < 2)
453 fprintf(stderr
, "Cannot connect plugin %s to output - incompatible ports\n", plugins
[last
]->name
.c_str());
455 client
.connect(cnp
+ plugins
[last
]->get_outputs()[0].name
, "system:playback_1");
456 client
.connect(cnp
+ plugins
[last
]->get_outputs()[1].name
, "system:playback_2");
459 if (autoconnect_midi
!= "") {
460 for (unsigned int i
= 0; i
< plugins
.size(); i
++)
462 if (plugins
[i
]->get_midi())
463 client
.connect(autoconnect_midi
, cnp
+ plugins
[i
]->get_midi_port()->name
);
468 send_lash(LASH_Client_Name
, "calf-"+client_name
);
469 lash_source_id
= g_timeout_add_full(G_PRIORITY_LOW
, 250, update_lash
, this, NULL
); // 4 LASH reads per second... should be enough?
473 void host_session::close()
476 g_source_remove(lash_source_id
);
477 main_win
->on_closed();
478 main_win
->close_guis();
480 client
.delete_plugins();
486 static string
stripfmt(string x
)
490 if (x
.substr(x
.length() - 2) != "%d")
492 return x
.substr(0, x
.length() - 2);
495 void host_session::update_lash()
498 lash_event_t
*event
= lash_get_event(lash_client
);
502 // printf("type = %d\n", lash_event_get_type(event));
504 switch(lash_event_get_type(event
)) {
505 case LASH_Save_Data_Set
:
507 lash_config_t
*cfg
= lash_config_new_with_key("global");
510 string i_name
= stripfmt(client
.input_name
);
511 string o_name
= stripfmt(client
.output_name
);
512 string m_name
= stripfmt(client
.midi_name
);
513 tmp
["input_prefix"] = i_name
;
514 tmp
["output_prefix"] = stripfmt(client
.output_name
);
515 tmp
["midi_prefix"] = stripfmt(client
.midi_name
);
516 pstr
= encode_map(tmp
);
517 lash_config_set_value(cfg
, pstr
.c_str(), pstr
.length());
518 lash_send_config(lash_client
, cfg
);
520 for (unsigned int i
= 0; i
< plugins
.size(); i
++) {
521 jack_host_base
*p
= plugins
[i
];
523 plugin_preset preset
;
524 preset
.plugin
= p
->get_id();
526 sprintf(ss
, "Plugin%d", i
);
527 pstr
= preset
.to_xml();
529 tmp
["instance_name"] = p
->instance_name
;
530 if (p
->get_input_count())
531 tmp
["input_name"] = p
->get_inputs()[0].name
.substr(i_name
.length());
532 if (p
->get_output_count())
533 tmp
["output_name"] = p
->get_outputs()[0].name
.substr(o_name
.length());
534 if (p
->get_midi_port())
535 tmp
["midi_name"] = p
->get_midi_port()->name
.substr(m_name
.length());
536 tmp
["preset"] = pstr
;
537 pstr
= encode_map(tmp
);
538 lash_config_t
*cfg
= lash_config_new_with_key(ss
);
539 lash_config_set_value(cfg
, pstr
.c_str(), pstr
.length());
540 lash_send_config(lash_client
, cfg
);
542 send_lash(LASH_Save_Data_Set
, "");
546 case LASH_Restore_Data_Set
:
548 // printf("!!!Restore data set!!!\n");
549 remove_all_plugins();
550 while(lash_config_t
*cfg
= lash_get_config(lash_client
)) {
551 const char *key
= lash_config_get_key(cfg
);
552 // printf("key = %s\n", lash_config_get_key(cfg));
553 string data
= string((const char *)lash_config_get_value(cfg
), lash_config_get_value_size(cfg
));
554 if (!strcmp(key
, "global"))
557 decode_map(dict
, data
);
558 if (dict
.count("input_prefix")) client
.input_name
= dict
["input_prefix"]+"%d";
559 if (dict
.count("output_prefix")) client
.output_name
= dict
["output_prefix"]+"%d";
560 if (dict
.count("midi_prefix")) client
.midi_name
= dict
["midi_prefix"]+"%d";
562 if (!strncmp(key
, "Plugin", 6))
564 unsigned int nplugin
= atoi(key
+ 6);
566 decode_map(dict
, data
);
567 data
= dict
["preset"];
568 string instance_name
;
569 if (dict
.count("instance_name")) instance_name
= dict
["instance_name"];
570 if (dict
.count("input_name")) client
.input_nr
= atoi(dict
["input_name"].c_str());
571 if (dict
.count("output_name")) client
.output_nr
= atoi(dict
["output_name"].c_str());
572 if (dict
.count("midi_name")) client
.midi_nr
= atoi(dict
["midi_name"].c_str());
574 tmp
.parse("<presets>"+data
+"</presets>");
575 if (tmp
.presets
.size())
577 printf("Load plugin %s\n", tmp
.presets
[0].plugin
.c_str());
578 add_plugin(tmp
.presets
[0].plugin
, "", instance_name
);
579 tmp
.presets
[0].activate(plugins
[nplugin
]);
580 main_win
->refresh_plugin(plugins
[nplugin
]);
583 lash_config_destroy(cfg
);
585 send_lash(LASH_Restore_Data_Set
, "");
594 g_warning("Unhandled LASH event %d (%s)", lash_event_get_type(event
), lash_event_get_string(event
));
601 host_session current_session
;
603 int main(int argc
, char *argv
[])
605 host_session
&sess
= current_session
;
606 gtk_init(&argc
, &argv
);
609 for (int i
= 1; i
< argc
; i
++)
611 if (!strncmp(argv
[i
], "--lash-project=", 14)) {
612 sess
.restoring_session
= true;
616 sess
.lash_args
= lash_extract_args(&argc
, &argv
);
617 sess
.lash_client
= lash_init(sess
.lash_args
, PACKAGE_NAME
, LASH_Config_Data_Set
, LASH_PROTOCOL(2, 0));
618 if (!sess
.lash_client
) {
619 g_warning("Warning: failed to create a LASH connection");
625 int c
= getopt_long(argc
, argv
, "c:i:o:m:M:ehv", long_options
, &option_index
);
634 printf("%s\n", PACKAGE_STRING
);
637 fprintf(stderr
, "Warning: switch -%c is deprecated!\n", c
);
640 sess
.client_name
= optarg
;
643 sess
.input_name
= string(optarg
) + "_%d";
646 sess
.output_name
= string(optarg
) + "_%d";
649 sess
.midi_name
= string(optarg
) + "_%d";
653 sess
.autoconnect_midi
= "system:midi_capture_" + string(optarg
);
655 sess
.autoconnect_midi
= string(optarg
);
659 while(optind
< argc
) {
660 if (!strcmp(argv
[optind
], "!")) {
661 sess
.chains
.insert(sess
.plugin_names
.size());
664 string plugname
= argv
[optind
++];
665 size_t pos
= plugname
.find(":");
666 if (pos
!= string::npos
) {
667 sess
.presets
[sess
.plugin_names
.size()] = plugname
.substr(pos
+ 1);
668 plugname
= plugname
.substr(0, pos
);
670 sess
.plugin_names
.push_back(plugname
);
674 get_builtin_presets().load_defaults(true);
675 get_user_presets().load_defaults(false);
677 catch(calf_plugins::preset_exception
&e
)
679 // XXXKF this exception is already handled by load_defaults, so this is redundant
680 fprintf(stderr
, "Error while loading presets: %s\n", e
.what());
686 sess
.client
.activate();
692 lash_args_destroy(sess
.lash_args
);
694 // this is now done on preset add
695 // save_presets(get_preset_filename().c_str());
697 catch(std::exception
&e
)
699 fprintf(stderr
, "%s\n", e
.what());