/* This file is part of Patchage. * Copyright (C) 2007 Dave Robillard * * Patchage is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * Patchage is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include "Patchage.h" #include "PatchageEvent.h" #include "config.h" #include #include #include #include #include "StateManager.h" #include "PatchageFlowCanvas.h" #include #include "JackDriver.h" #include "JackSettingsDialog.h" #ifdef HAVE_LASH #include "LashDriver.h" #endif #ifdef HAVE_ALSA #include "AlsaDriver.h" #endif // FIXME: include to avoid undefined reference to boost SP debug hooks stuff #include /* Gtk helpers (resize combo boxes) */ static void gtkmm_get_ink_pixel_size (Glib::RefPtr layout, int& width, int& height) { Pango::Rectangle ink_rect = layout->get_ink_extents (); width = (ink_rect.get_width() + PANGO_SCALE / 2) / PANGO_SCALE; height = (ink_rect.get_height() + PANGO_SCALE / 2) / PANGO_SCALE; } static void gtkmm_set_width_for_given_text (Gtk::Widget &w, const gchar *text, gint hpadding/*, gint vpadding*/) { int old_width, old_height; w.get_size_request(old_width, old_height); int width, height; w.ensure_style (); gtkmm_get_ink_pixel_size (w.create_pango_layout (text), width, height); w.set_size_request(width + hpadding, old_height);//height + vpadding); } /* end Gtk helpers */ Patchage::Patchage(int argc, char** argv) : _pane_closed(false), _update_pane_position(true), _user_pane_position(0), #ifdef HAVE_LASH _lash_driver(NULL), #endif #ifdef HAVE_ALSA _alsa_driver(NULL), #endif _jack_driver(NULL), _state_manager(NULL), _refresh(false) { _settings_filename = getenv("HOME"); _settings_filename += "/.patchagerc"; _state_manager = new StateManager(); _canvas = boost::shared_ptr(new PatchageFlowCanvas(this, 1600*2, 1200*2)); _jack_driver = new JackDriver(this); _jack_driver->signal_detached.connect(sigc::mem_fun(this, &Patchage::queue_refresh)); #ifdef HAVE_ALSA _alsa_driver = new AlsaDriver(this); #endif _state_manager->load(_settings_filename); #ifdef HAVE_LASH _lash_driver = new LashDriver(this, argc, argv); #endif Glib::RefPtr xml; // Check for the .glade file in current directory string glade_filename = "./patchage.glade"; ifstream fs(glade_filename.c_str()); if (fs.fail()) { // didn't find it, check PKGDATADIR fs.clear(); glade_filename = PKGDATADIR; glade_filename += "/patchage.glade"; fs.open(glade_filename.c_str()); if (fs.fail()) { cerr << "Unable to find patchage.glade in current directory or " << PKGDATADIR << "." << endl; exit(EXIT_FAILURE); } fs.close(); } xml = Gnome::Glade::Xml::create(glade_filename); xml->get_widget("patchage_win", _main_window); xml->get_widget_derived("jack_settings_win", _jack_settings_dialog); xml->get_widget("about_win", _about_window); xml->get_widget("jack_settings_menuitem", _menu_jack_settings); xml->get_widget("connect_to_jack_menuitem", _menu_jack_connect); xml->get_widget("disconnect_from_jack_menuitem", _menu_jack_disconnect); #ifdef HAVE_LASH xml->get_widget("open_session_menuitem", _menu_open_session); xml->get_widget("save_session_menuitem", _menu_save_session); xml->get_widget("save_session_as_menuitem", _menu_save_session_as); xml->get_widget("close_session_menuitem", _menu_close_session); xml->get_widget("connect_to_lash_menuitem", _menu_lash_connect); xml->get_widget("disconnect_from_lash_menuitem", _menu_lash_disconnect); #endif #ifdef HAVE_ALSA xml->get_widget("connect_to_alsa_menuitem", _menu_alsa_connect); xml->get_widget("disconnect_from_alsa_menuitem", _menu_alsa_disconnect); #endif xml->get_widget("store_positions_menuitem", _menu_store_positions); xml->get_widget("file_quit_menuitem", _menu_file_quit); xml->get_widget("view_refresh_menuitem", _menu_view_refresh); xml->get_widget("view_messages_menuitem", _menu_view_messages); xml->get_widget("view_toolbar_menuitem", _menu_view_toolbar); xml->get_widget("help_about_menuitem", _menu_help_about); xml->get_widget("toolbar", _toolbar); xml->get_widget("canvas_scrolledwindow", _canvas_scrolledwindow); xml->get_widget("zoom_scale", _zoom_slider); xml->get_widget("status_text", _status_text); xml->get_widget("main_paned", _main_paned); xml->get_widget("messages_expander", _messages_expander); xml->get_widget("rewind_but", _rewind_button); xml->get_widget("play_but", _play_button); xml->get_widget("stop_but", _stop_button); xml->get_widget("zoom_full_but", _zoom_full_button); xml->get_widget("zoom_normal_but", _zoom_normal_button); //xml->get_widget("main_statusbar", _status_bar); //xml->get_widget("main_load_progress", _load_progress_bar); //xml->get_widget("main_jack_connect_toggle", _jack_connect_toggle); //xml->get_widget("main_jack_realtime_check", _jack_realtime_check); xml->get_widget("main_buffer_size_combo", _buffer_size_combo); xml->get_widget("main_sample_rate_label", _sample_rate_label); xml->get_widget("main_xrun_progress", _xrun_progress_bar); xml->get_widget("main_clear_load_button", _clear_load_button); gtkmm_set_width_for_given_text(*_buffer_size_combo, "4096 frames", 40); //gtkmm_set_width_for_given_text(*m_sample_rate_combo, "44.1", 40); _canvas_scrolledwindow->add(*_canvas); //m_canvas_scrolledwindow->signal_event().connect(sigc::mem_fun(_canvas, &FlowCanvas::scroll_event_handler)); _canvas->scroll_to(static_cast(_canvas->width()/2 - 320), static_cast(_canvas->height()/2 - 240)); // FIXME: hardcoded _zoom_slider->signal_value_changed().connect(sigc::mem_fun(this, &Patchage::zoom_changed)); //_jack_connect_toggle->signal_toggled().connect(sigc::mem_fun(this, &Patchage::jack_connect_changed)); _buffer_size_combo->signal_changed().connect(sigc::mem_fun(this, &Patchage::buffer_size_changed)); //m_sample_rate_combo->signal_changed().connect(sigc::mem_fun(this, &Patchage::sample_rate_changed)); //_jack_realtime_check->signal_toggled().connect(sigc::mem_fun(this, &Patchage::realtime_changed)); _rewind_button->signal_clicked().connect(sigc::mem_fun(_jack_driver, &JackDriver::rewind_transport)); _play_button->signal_clicked().connect(sigc::mem_fun(_jack_driver, &JackDriver::start_transport)); _stop_button->signal_clicked().connect(sigc::mem_fun(_jack_driver, &JackDriver::stop_transport)); _clear_load_button->signal_clicked().connect(sigc::mem_fun(this, &Patchage::clear_load)); _zoom_normal_button->signal_clicked().connect(sigc::bind( sigc::mem_fun(this, &Patchage::zoom), 1.0)); _zoom_full_button->signal_clicked().connect(sigc::mem_fun(_canvas.get(), &PatchageFlowCanvas::zoom_full)); _menu_jack_settings->signal_activate().connect( sigc::hide_return(sigc::mem_fun(_jack_settings_dialog, &JackSettingsDialog::run))); _menu_jack_connect->signal_activate().connect(sigc::bind( sigc::mem_fun(_jack_driver, &JackDriver::attach), true)); _menu_jack_disconnect->signal_activate().connect(sigc::mem_fun(_jack_driver, &JackDriver::detach)); #ifdef HAVE_LASH _menu_open_session->signal_activate().connect( sigc::mem_fun(this, &Patchage::menu_open_session)); _menu_save_session->signal_activate().connect( sigc::mem_fun(this, &Patchage::menu_save_session)); _menu_save_session_as->signal_activate().connect(sigc::mem_fun(this, &Patchage::menu_save_session_as)); _menu_close_session->signal_activate().connect(sigc::mem_fun(this, &Patchage::menu_close_session)); _menu_lash_connect->signal_activate().connect( sigc::mem_fun(this, &Patchage::menu_lash_connect)); _menu_lash_disconnect->signal_activate().connect(sigc::mem_fun(this, &Patchage::menu_lash_disconnect)); #endif #ifdef HAVE_ALSA _menu_alsa_connect->signal_activate().connect( sigc::mem_fun(this, &Patchage::menu_alsa_connect)); _menu_alsa_disconnect->signal_activate().connect(sigc::mem_fun(this, &Patchage::menu_alsa_disconnect)); #endif _menu_store_positions->signal_activate().connect(sigc::mem_fun(this, &Patchage::menu_store_positions)); _menu_file_quit->signal_activate().connect( sigc::mem_fun(this, &Patchage::menu_file_quit)); _menu_view_refresh->signal_activate().connect( sigc::mem_fun(this, &Patchage::menu_view_refresh)); _menu_view_toolbar->signal_activate().connect( sigc::mem_fun(this, &Patchage::view_toolbar_toggled)); _menu_view_messages->signal_toggled().connect( sigc::mem_fun(this, &Patchage::show_messages_toggled)); _menu_help_about->signal_activate().connect( sigc::mem_fun(this, &Patchage::menu_help_about)); connect_widgets(); update_state(); _canvas->show(); _main_window->present(); _update_pane_position = false; _main_paned->set_position(max_pane_position()); _user_pane_position = max_pane_position() - _main_window->get_height()/8; _messages_expander->set_expanded(false); _pane_closed = true; _main_paned->property_position().signal_changed().connect( sigc::mem_fun(*this, &Patchage::on_pane_position_changed)); _messages_expander->property_expanded().signal_changed().connect( sigc::mem_fun(*this, &Patchage::on_messages_expander_changed)); // Idle callback, check if we need to refresh Glib::signal_timeout().connect(sigc::mem_fun(this, &Patchage::idle_callback), 100); // Faster idle callback to update DSP load progress bar //Glib::signal_timeout().connect(sigc::mem_fun(this, &Patchage::update_load), 50); _update_pane_position = true; } Patchage::~Patchage() { delete _jack_driver; #ifdef HAVE_ALSA delete _alsa_driver; #endif #ifdef HAVE_LASH delete _lash_driver; #endif delete _state_manager; } void Patchage::attach() { _jack_driver->attach(true); #ifdef HAVE_LASH _lash_driver->attach(true); #endif #ifdef HAVE_ALSA _alsa_driver->attach(); #endif menu_view_refresh(); update_toolbar(); //m_status_bar->push("Connected to JACK server"); } bool Patchage::idle_callback() { if (_jack_driver) { while (!_jack_driver->events().empty()) { PatchageEvent& ev = _jack_driver->events().front(); _jack_driver->events().pop(); ev.execute(); } } bool refresh = _refresh; refresh = refresh || (_jack_driver && _jack_driver->is_dirty()); #ifdef HAVE_ALSA refresh = refresh || (_alsa_driver && _alsa_driver->is_dirty()); #endif if (refresh) { _canvas->flag_all_connections(); _jack_driver->refresh(); #ifdef HAVE_ALSA _alsa_driver->refresh(); #endif } #ifdef HAVE_LASH if (_lash_driver->is_attached()) _lash_driver->process_events(); #endif if (refresh) { _canvas->destroy_all_flagged_connections(); _refresh = false; } update_load(); return true; } void Patchage::update_toolbar() { //_jack_connect_toggle->set_active(_jack_driver->is_attached()); //_jack_realtime_check->set_active(_jack_driver->is_realtime()); if (_jack_driver->is_attached()) { _buffer_size_combo->set_active((int)log2f(_jack_driver->buffer_size()) - 5); /*switch ((int)m_jack_driver->sample_rate()) { case 44100: _sample_rate_combo->set_active(0); break; case 48000: _sample_rate_combo->set_active(1); break; case 96000: _sample_rate_combo->set_active(2); break; default: _sample_rate_combo->set_active(-1); status_message("[JACK] ERROR: Unknown sample rate"); break; }*/ stringstream srate; srate << _jack_driver->sample_rate()/1000.0; _sample_rate_label->set_text(srate.str()); } } bool Patchage::update_load() { if (!_jack_driver->is_attached()) return true; static float last_delay = 0; const float max_delay = _jack_driver->max_delay(); if (max_delay != last_delay) { const float sample_rate = _jack_driver->sample_rate(); const float buffer_size = _jack_driver->buffer_size(); const float period = buffer_size / sample_rate * 1000000; // usecs /* if (max_delay > 0) { cerr << "SR: " << sample_rate << ", BS: " << buffer_size << ", P = " << period << ", MD: " << max_delay << endl; }*/ _xrun_progress_bar->set_fraction(max_delay / period); char tmp_buf[8]; snprintf(tmp_buf, 8, "%zd", _jack_driver->xruns()); _xrun_progress_bar->set_text(string(tmp_buf) + " Dropouts"); if (max_delay > period) { _xrun_progress_bar->set_fraction(1.0); _jack_driver->reset_delay(); } last_delay = max_delay; } return true; } void Patchage::zoom(double z) { _state_manager->set_zoom(z); _canvas->set_zoom(z); } void Patchage::zoom_changed() { static bool enable_signal = true; if (enable_signal) { enable_signal = false; zoom(_zoom_slider->get_value()); enable_signal = true; } } void Patchage::update_state() { for (ItemList::iterator i = _canvas->items().begin(); i != _canvas->items().end(); ++i) { SharedPtr module = PtrCast(*i); if (module) module->load_location(); } //cerr << "[Patchage] Resizing window: (" << _state_manager->get_window_size().x // << "," << _state_manager->get_window_size().y << ")" << endl; _main_window->resize( static_cast(_state_manager->get_window_size().x), static_cast(_state_manager->get_window_size().y)); //cerr << "[Patchage] Moving window: (" << _state_manager->get_window_location().x // << "," << _state_manager->get_window_location().y << ")" << endl; _main_window->move( static_cast(_state_manager->get_window_location().x), static_cast(_state_manager->get_window_location().y)); } void Patchage::status_message(const string& msg) { if (_status_text->get_buffer()->size() > 0) _status_text->get_buffer()->insert(_status_text->get_buffer()->end(), "\n"); _status_text->get_buffer()->insert(_status_text->get_buffer()->end(), msg); _status_text->scroll_to_mark(_status_text->get_buffer()->get_insert(), 0); } /** Update the sensitivity status of menus to reflect the present. * * (eg. disable "Connect to Jack" when Patchage is already connected to Jack) */ void Patchage::connect_widgets() { #ifdef HAVE_LASH _lash_driver->signal_attached.connect(sigc::bind( sigc::mem_fun(_menu_lash_connect, &Gtk::MenuItem::set_sensitive), false)); _lash_driver->signal_attached.connect(sigc::bind( sigc::mem_fun(_menu_lash_disconnect, &Gtk::MenuItem::set_sensitive), true)); _lash_driver->signal_detached.connect(sigc::bind( sigc::mem_fun(_menu_lash_connect, &Gtk::MenuItem::set_sensitive), true)); _lash_driver->signal_detached.connect(sigc::bind( sigc::mem_fun(_menu_lash_disconnect, &Gtk::MenuItem::set_sensitive), false)); #endif _jack_driver->signal_attached.connect( sigc::mem_fun(this, &Patchage::update_toolbar)); //_jack_driver->signal_attached.connect(sigc::bind( /// sigc::mem_fun(_jack_connect_toggle, &Gtk::ToggleButton::set_active), true)); _jack_driver->signal_attached.connect(sigc::bind( sigc::mem_fun(_menu_jack_connect, &Gtk::MenuItem::set_sensitive), false)); _jack_driver->signal_attached.connect(sigc::bind( sigc::mem_fun(_menu_jack_disconnect, &Gtk::MenuItem::set_sensitive), true)); //_jack_driver->signal_detached.connect(sigc::bind( // sigc::mem_fun(_jack_connect_toggle, &Gtk::ToggleButton::set_active), false)); _jack_driver->signal_detached.connect(sigc::bind( sigc::mem_fun(_menu_jack_connect, &Gtk::MenuItem::set_sensitive), true)); _jack_driver->signal_detached.connect(sigc::bind( sigc::mem_fun(_menu_jack_disconnect, &Gtk::MenuItem::set_sensitive), false)); #ifdef HAVE_ALSA _alsa_driver->signal_attached.connect(sigc::bind( sigc::mem_fun(_menu_alsa_connect, &Gtk::MenuItem::set_sensitive), false)); _alsa_driver->signal_attached.connect(sigc::bind( sigc::mem_fun(_menu_alsa_disconnect, &Gtk::MenuItem::set_sensitive), true)); _alsa_driver->signal_detached.connect(sigc::bind( sigc::mem_fun(_menu_alsa_connect, &Gtk::MenuItem::set_sensitive), true)); _alsa_driver->signal_detached.connect(sigc::bind( sigc::mem_fun(_menu_alsa_disconnect, &Gtk::MenuItem::set_sensitive), false)); #endif } #ifdef HAVE_LASH void Patchage::menu_open_session() { Gtk::FileChooserDialog dialog(*_main_window, "Open LASH Session", Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER); dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); dialog.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK); const int result = dialog.run(); if (result == Gtk::RESPONSE_OK) { _lash_driver->restore_project(dialog.get_filename()); } } void Patchage::menu_save_session() { if (_lash_driver) _lash_driver->save_project(); } void Patchage::menu_save_session_as() { if (!_lash_driver) return; Gtk::FileChooserDialog dialog(*_main_window, "Save LASH Session", Gtk::FILE_CHOOSER_ACTION_SAVE); dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); dialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK); const int result = dialog.run(); if (result == Gtk::RESPONSE_OK) { _lash_driver->set_project_directory(dialog.get_filename()); _lash_driver->save_project(); } } void Patchage::menu_close_session() { cerr << "CLOSE SESSION\n"; _lash_driver->close_project(); } void Patchage::menu_lash_connect() { _lash_driver->attach(true); } void Patchage::menu_lash_disconnect() { _lash_driver->detach(); } #endif #ifdef HAVE_ALSA void Patchage::menu_alsa_connect() { _alsa_driver->attach(false); } void Patchage::menu_alsa_disconnect() { _alsa_driver->detach(); menu_view_refresh(); } #endif void Patchage::menu_store_positions() { store_window_location(); _state_manager->save(_settings_filename); } void Patchage::menu_file_quit() { #ifdef HAVE_ALSA _alsa_driver->detach(); #endif _jack_driver->detach(); _main_window->hide(); } void Patchage::on_pane_position_changed() { // avoid infinite recursion... if (!_update_pane_position) return; _update_pane_position = false; int new_position = _main_paned->get_position(); if (_pane_closed && new_position < max_pane_position()) { // Auto open _user_pane_position = new_position; _messages_expander->set_expanded(true); _pane_closed = false; _menu_view_messages->set_active(true); } else if (new_position >= max_pane_position()) { // Auto close _pane_closed = true; _messages_expander->set_expanded(false); if (new_position > max_pane_position()) _main_paned->set_position(max_pane_position()); // ... here _menu_view_messages->set_active(false); _user_pane_position = max_pane_position() - _main_window->get_height()/8; } _update_pane_position = true; } void Patchage::on_messages_expander_changed() { if (!_update_pane_position) return; if (!_pane_closed) { // Store pane position for restoring _user_pane_position = _main_paned->get_position(); if (_update_pane_position) { _update_pane_position = false; _main_paned->set_position(max_pane_position()); _update_pane_position = true; } _pane_closed = true; } else { _main_paned->set_position(_user_pane_position); _pane_closed = false; } } void Patchage::show_messages_toggled() { if (_update_pane_position) _messages_expander->set_expanded(_menu_view_messages->get_active()); } void Patchage::menu_view_refresh() { assert(_canvas); _canvas->destroy(); if (_jack_driver) _jack_driver->refresh(); #ifdef HAVE_ALSA if (_alsa_driver) _alsa_driver->refresh(); #endif } void Patchage::view_toolbar_toggled() { _update_pane_position = false; if (_menu_view_toolbar->get_active()) _toolbar->show(); else _toolbar->hide(); _update_pane_position = true; } void Patchage::menu_help_about() { _about_window->show(); } /** Update the stored window location and size in the StateManager (in memory). */ void Patchage::store_window_location() { int loc_x, loc_y, size_x, size_y; _main_window->get_position(loc_x, loc_y); _main_window->get_size(size_x, size_y); Coord window_location; window_location.x = loc_x; window_location.y = loc_y; Coord window_size; window_size.x = size_x; window_size.y = size_y; _state_manager->set_window_location(window_location); _state_manager->set_window_size(window_size); } void Patchage::clear_load() { _xrun_progress_bar->set_fraction(0.0); _jack_driver->reset_xruns(); _jack_driver->reset_delay(); } void Patchage::buffer_size_changed() { const int selected = _buffer_size_combo->get_active_row_number(); if (selected == -1) { update_toolbar(); } else { jack_nframes_t buffer_size = 1 << (selected+5); //cerr << "BS Changed: " << selected << ": " << buffer_size << endl; if ( ! _jack_driver->set_buffer_size(buffer_size)) update_toolbar(); // reset combo box to actual value } } /* void Patchage::sample_rate_changed() { const int selected = _sample_rate_combo->get_active_row_number(); if (selected == -1) { update_toolbar(); } else { jack_nframes_t rate = 44100; // selected == 0 if (selected == 1) rate = 48000; else if (selected == 2) rate = 96000; //cerr << "SR Changed: " << selected << ": " << rate << endl; //m_jack_driver->set_sample_rate(rate); } } void Patchage::realtime_changed() { _jack_driver->set_realtime(_jack_realtime_check->get_active()); } void Patchage::jack_connect_changed() { const bool selected = _jack_connect_toggle->get_active(); if (selected != _jack_driver->is_attached()) { if (selected) { _jack_driver->attach(true); } else { _jack_driver->detach(); } } } */