--- gigedit/branches/linuxsampler_org/src/mainwindow.cpp 2007/03/03 12:20:01 1052 +++ gigedit/trunk/src/mainwindow.cpp 2007/03/09 19:45:45 1087 @@ -27,10 +27,19 @@ #if GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION >= 6 #define ABOUT_DIALOG #include +#include #endif +#include +#include + #define _(String) gettext(String) +template inline std::string ToString(T o) { + std::stringstream ss; + ss << o; + return ss.str(); +} bool update_gui; @@ -524,8 +533,8 @@ m_TreeView.signal_button_press_event().connect_notify( sigc::mem_fun(*this, &MainWindow::on_button_release)); - // Add the TreeView, inside a ScrolledWindow, with the button underneath: - m_ScrolledWindow.add(m_TreeView); + // Add the TreeView tab, inside a ScrolledWindow, with the button underneath: + m_ScrolledWindow.add(m_TreeViewNotebook); m_ScrolledWindow.set_size_request(400, 600); m_ScrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); @@ -784,6 +793,10 @@ m_HPaned.add2(m_Notebook); + m_TreeViewNotebook.append_page(m_TreeViewSamples, "Samples"); + m_TreeViewNotebook.append_page(m_TreeView, "Instruments"); + + actionGroup = Gtk::ActionGroup::create(); actionGroup->add(Gtk::Action::create("MenuFile", _("_File"))); @@ -817,6 +830,8 @@ actionGroup->add(Gtk::Action::create("Quit", Gtk::Stock::QUIT), sigc::mem_fun( *this, &MainWindow::hide)); + actionGroup->add(Gtk::Action::create("MenuInstrument", _("_Instrument"))); + action = Gtk::Action::create("MenuHelp", Gtk::Stock::HELP); actionGroup->add(Gtk::Action::create("MenuHelp", action->property_label())); @@ -825,11 +840,29 @@ sigc::mem_fun( *this, &MainWindow::on_action_help_about)); #endif - action = Gtk::Action::create("Remove", "Ta bort"); + action = Gtk::Action::create("Remove", Gtk::Stock::REMOVE); actionGroup->add(action, sigc::mem_fun( *this, &MainWindow::hide)); + // sample right-click popup actions + actionGroup->add( + Gtk::Action::create("SampleProperties", Gtk::Stock::PROPERTIES), + sigc::mem_fun(*this, &MainWindow::on_action_sample_properties) + ); + actionGroup->add( + Gtk::Action::create("AddGroup", _("Add _Group")), + sigc::mem_fun(*this, &MainWindow::on_action_add_group) + ); + actionGroup->add( + Gtk::Action::create("AddSample", _("Add _Sample(s)")), + sigc::mem_fun(*this, &MainWindow::on_action_add_sample) + ); + actionGroup->add( + Gtk::Action::create("RemoveSample", Gtk::Stock::REMOVE), + sigc::mem_fun(*this, &MainWindow::on_action_remove_sample) + ); + uiManager = Gtk::UIManager::create(); uiManager->insert_action_group(actionGroup); // add_accel_group(uiManager->get_accel_group()); @@ -848,6 +881,8 @@ " " " " " " + " " + " " #ifdef ABOUT_DIALOG " " " " @@ -858,6 +893,13 @@ " " " " " " + " " + " " + " " + " " + " " + " " + " " ""; uiManager->add_ui_from_string(ui_info); @@ -883,6 +925,15 @@ m_TreeView.append_column("Instrument", m_Columns.m_col_name); m_TreeView.set_headers_visible(false); + // create samples treeview (including its data model) + m_refSamplesTreeModel = Gtk::TreeStore::create(m_SamplesModel); + m_TreeViewSamples.set_model(m_refSamplesTreeModel); + m_TreeViewSamples.append_column("Samples", m_SamplesModel.m_col_name); + m_TreeViewSamples.set_headers_visible(false); + m_TreeViewSamples.signal_button_press_event().connect_notify( + sigc::mem_fun(*this, &MainWindow::on_sample_treeview_button_release) + ); + file = 0; show_all_children(); @@ -1229,6 +1280,7 @@ void MainWindow::on_action_file_new() { + m_SampleImportQueue.clear(); } void MainWindow::on_action_file_open() @@ -1242,7 +1294,18 @@ if (dialog.run() == Gtk::RESPONSE_OK) { printf("filename=%s\n", dialog.get_filename().c_str()); + // remove all entries from "Instrument" menu + Gtk::MenuItem* instrument_menu = + dynamic_cast(uiManager->get_widget("/MenuBar/MenuInstrument")); + instrument_menu->hide(); + for (int i = 0; i < instrument_menu->get_submenu()->items().size(); i++) { + delete &instrument_menu->get_submenu()->items()[i]; + } + instrument_menu->get_submenu()->items().clear(); + + m_SampleImportQueue.clear(); m_refTreeModel->clear(); + m_refSamplesTreeModel->clear(); if (file) delete file; // getInfo(dialog.get_filename().c_str(), *this); @@ -1280,10 +1343,14 @@ void MainWindow::on_action_file_save() { + if (!file) return; + file->Save(); + __import_queued_samples(); } void MainWindow::on_action_file_save_as() { + if (!file) return; Gtk::FileChooserDialog dialog(*this, "Open", Gtk::FILE_CHOOSER_ACTION_SAVE); dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); dialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK); @@ -1293,6 +1360,78 @@ if (dialog.run() == Gtk::RESPONSE_OK) { printf("filename=%s\n", dialog.get_filename().c_str()); file->Save(dialog.get_filename()); + __import_queued_samples(); + } +} + +// actually write the sample(s)' data to the gig file +void MainWindow::__import_queued_samples() { + Glib::ustring error_files; + for (std::list::iterator iter = m_SampleImportQueue.begin(); iter != m_SampleImportQueue.end(); ++iter) { + printf("Importing sample %s\n",(*iter).sample_path.c_str()); + SF_INFO info; + info.format = 0; + SNDFILE* hFile = sf_open((*iter).sample_path.c_str(), SFM_READ, &info); + try { + if (!hFile) throw std::string("could not open file"); + // determine sample's bit depth + int bitdepth; + switch (info.format & 0xff) { + case SF_FORMAT_PCM_S8: + bitdepth = 16; // we simply convert to 16 bit for now + break; + case SF_FORMAT_PCM_16: + bitdepth = 16; + break; + case SF_FORMAT_PCM_24: + bitdepth = 32; // we simply convert to 32 bit for now + break; + case SF_FORMAT_PCM_32: + bitdepth = 32; + break; + case SF_FORMAT_PCM_U8: + bitdepth = 16; // we simply convert to 16 bit for now + break; + case SF_FORMAT_FLOAT: + bitdepth = 32; + break; + case SF_FORMAT_DOUBLE: + bitdepth = 32; // I guess we will always truncate this to 32 bit + break; + default: + sf_close(hFile); // close sound file + throw std::string("format not supported"); // unsupported subformat (yet?) + } + // allocate appropriate copy buffer (TODO: for now we copy it in one piece, might be tough for very long samples) + // and copy sample data into buffer + int8_t* buffer = NULL; + switch (bitdepth) { + case 16: + buffer = new int8_t[2 * info.channels * info.frames]; + sf_readf_short(hFile, (short*) buffer, info.frames); // libsndfile does the conversion for us (if needed) + break; + case 32: + buffer = new int8_t[4 * info.channels * info.frames]; + sf_readf_int(hFile, (int*) buffer, info.frames); // libsndfile does the conversion for us (if needed) + break; + } + // write from buffer directly (physically) into .gig file + (*iter).gig_sample->Write(buffer, info.frames); + // cleanup + sf_close(hFile); + delete buffer; + // on success we remove the sample from the import queue, otherwise keep it, maybe it works the next time ? + m_SampleImportQueue.erase(iter); + } catch (std::string what) { // remember the files that made trouble (and their cause) + if (error_files.size()) error_files += "\n"; + error_files += (*iter).sample_path += " (" + what + ")"; + } + } + // show error message box when some sample(s) could not be imported + if (error_files.size()) { + Glib::ustring txt = "Could not import the following sample(s):\n" + error_files; + Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR); + msg.run(); } } @@ -1474,12 +1613,44 @@ propDialog.set_info(gig->pInfo); + Gtk::MenuItem* instrument_menu = + dynamic_cast(uiManager->get_widget("/MenuBar/MenuInstrument")); + + int instrument_index = 0; + Gtk::RadioMenuItem::Group instrument_group; for (gig::Instrument* instrument = gig->GetFirstInstrument() ; instrument ; instrument = gig->GetNextInstrument()) { Gtk::TreeModel::iterator iter = m_refTreeModel->append(); Gtk::TreeModel::Row row = *iter; row[m_Columns.m_col_name] = instrument->pInfo->Name.c_str(); row[m_Columns.m_col_instr] = instrument; + // create a menu item for this instrument + Gtk::RadioMenuItem* item= new Gtk::RadioMenuItem(instrument_group, instrument->pInfo->Name.c_str()); + instrument_menu->get_submenu()->append(*item); + item->signal_activate().connect( + sigc::bind( + sigc::mem_fun(*this, &MainWindow::on_instrument_selection_change), + instrument_index + ) + ); + instrument_index++; + } + instrument_menu->show(); + instrument_menu->get_submenu()->show_all_children(); + + for (gig::Group* group = gig->GetFirstGroup(); group; group = gig->GetNextGroup()) { + Gtk::TreeModel::iterator iterGroup = m_refSamplesTreeModel->append(); + Gtk::TreeModel::Row rowGroup = *iterGroup; + rowGroup[m_SamplesModel.m_col_name] = group->Name.c_str(); + rowGroup[m_SamplesModel.m_col_group] = group; + rowGroup[m_SamplesModel.m_col_sample] = NULL; + for (gig::Sample* sample = group->GetFirstSample(); sample; sample = group->GetNextSample()) { + Gtk::TreeModel::iterator iterSample = m_refSamplesTreeModel->append(rowGroup.children()); + Gtk::TreeModel::Row rowSample = *iterSample; + rowSample[m_SamplesModel.m_col_name] = sample->pInfo->Name.c_str(); + rowSample[m_SamplesModel.m_col_sample] = sample; + rowSample[m_SamplesModel.m_col_group] = NULL; + } } } @@ -1502,3 +1673,186 @@ popup_menu->popup(button->button, button->time); } } + +void MainWindow::on_instrument_selection_change(int index) { + m_RegionChooser.set_instrument(file->GetInstrument(index)); +} + +void MainWindow::on_sample_treeview_button_release(GdkEventButton* button) { + if (button->type == GDK_BUTTON_PRESS && button->button == 3) { + Gtk::Menu* sample_popup = + dynamic_cast(uiManager->get_widget("/SamplePopupMenu")); + // update enabled/disabled state of sample popup items + Glib::RefPtr sel = m_TreeViewSamples.get_selection(); + Gtk::TreeModel::iterator it = sel->get_selected(); + bool group_selected = false; + bool sample_selected = false; + if (it) { + Gtk::TreeModel::Row row = *it; + group_selected = row[m_SamplesModel.m_col_group]; + sample_selected = row[m_SamplesModel.m_col_sample]; + } + dynamic_cast(uiManager->get_widget("/SamplePopupMenu/SampleProperties"))->set_sensitive(group_selected || sample_selected); + dynamic_cast(uiManager->get_widget("/SamplePopupMenu/AddSample"))->set_sensitive(group_selected || sample_selected); + dynamic_cast(uiManager->get_widget("/SamplePopupMenu/AddGroup"))->set_sensitive(file); + dynamic_cast(uiManager->get_widget("/SamplePopupMenu/RemoveSample"))->set_sensitive(group_selected || sample_selected); + // show sample popup + sample_popup->popup(button->button, button->time); + } +} + +void MainWindow::on_action_sample_properties() { + //TODO: show a dialog where the selected sample's properties can be edited +} + +void MainWindow::on_action_add_group() { + static int __sample_indexer = 0; + if (!file) return; + gig::Group* group = file->AddGroup(); + group->Name = "Unnamed Group"; + if (__sample_indexer) group->Name += " " + ToString(__sample_indexer); + __sample_indexer++; + // update sample tree view + Gtk::TreeModel::iterator iterGroup = m_refSamplesTreeModel->append(); + Gtk::TreeModel::Row rowGroup = *iterGroup; + rowGroup[m_SamplesModel.m_col_name] = group->Name.c_str(); + rowGroup[m_SamplesModel.m_col_sample] = NULL; + rowGroup[m_SamplesModel.m_col_group] = group; +} + +void MainWindow::on_action_add_sample() { + if (!file) return; + // get selected group + Glib::RefPtr sel = m_TreeViewSamples.get_selection(); + Gtk::TreeModel::iterator it = sel->get_selected(); + if (!it) return; + Gtk::TreeModel::Row row = *it; + gig::Group* group = row[m_SamplesModel.m_col_group]; + if (!group) { // not a group, but a sample is selected (probably) + gig::Sample* sample = row[m_SamplesModel.m_col_sample]; + if (!sample) return; + it = row.parent(); // resolve parent (that is the sample's group) + if (!it) return; + row = *it; + group = row[m_SamplesModel.m_col_group]; + if (!group) return; + } + // show 'browse for file' dialog + Gtk::FileChooserDialog dialog(*this, _("Add Sample(s)")); + dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); + dialog.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK); + dialog.set_select_multiple(true); + Gtk::FileFilter soundfilter; // matches all file types supported by libsndfile (yet to do ;-) + soundfilter.add_pattern("*.wav"); + soundfilter.set_name("Sound Files"); + Gtk::FileFilter allpassfilter; // matches every file + allpassfilter.add_pattern("*.*"); + allpassfilter.set_name("All Files"); + dialog.add_filter(soundfilter); + dialog.add_filter(allpassfilter); + if (dialog.run() == Gtk::RESPONSE_OK) { + Glib::ustring error_files; + Glib::SListHandle filenames = dialog.get_filenames(); + for (Glib::SListHandle::iterator iter = filenames.begin(); iter != filenames.end(); ++iter) { + printf("Adding sample %s\n",(*iter).c_str()); + // use libsndfile to retrieve file informations + SF_INFO info; + info.format = 0; + SNDFILE* hFile = sf_open((*iter).c_str(), SFM_READ, &info); + try { + if (!hFile) throw std::string("could not open file"); + int bitdepth; + switch (info.format & 0xff) { + case SF_FORMAT_PCM_S8: + bitdepth = 16; // we simply convert to 16 bit for now + break; + case SF_FORMAT_PCM_16: + bitdepth = 16; + break; + case SF_FORMAT_PCM_24: + bitdepth = 32; // we simply convert to 32 bit for now + break; + case SF_FORMAT_PCM_32: + bitdepth = 32; + break; + case SF_FORMAT_PCM_U8: + bitdepth = 16; // we simply convert to 16 bit for now + break; + case SF_FORMAT_FLOAT: + bitdepth = 32; + break; + case SF_FORMAT_DOUBLE: + bitdepth = 32; // I guess we will always truncate this to 32 bit + break; + default: + sf_close(hFile); // close sound file + throw std::string("format not supported"); // unsupported subformat (yet?) + } + // add a new sample to the .gig file + gig::Sample* sample = file->AddSample(); + sample->pInfo->Name = (*iter).substr((*iter).rfind('/') + 1).raw(); // file name without path + sample->Channels = info.channels; + sample->BitDepth = bitdepth; + sample->FrameSize = bitdepth / 8/*1 byte are 8 bits*/ * info.channels; + sample->SamplesPerSecond = info.samplerate; + // schedule resizing the sample (which will be done physically when File::Save() is called) + sample->Resize(info.frames); + // schedule that physical resize and sample import (data copying), performed when "Save" is requested + SampleImportItem sched_item; + sched_item.gig_sample = sample; + sched_item.sample_path = *iter; + m_SampleImportQueue.push_back(sched_item); + // add sample to the tree view + Gtk::TreeModel::iterator iterSample = m_refSamplesTreeModel->append(row.children()); + Gtk::TreeModel::Row rowSample = *iterSample; + rowSample[m_SamplesModel.m_col_name] = sample->pInfo->Name.c_str(); + rowSample[m_SamplesModel.m_col_sample] = sample; + rowSample[m_SamplesModel.m_col_group] = NULL; + // close sound file + sf_close(hFile); + } catch (std::string what) { // remember the files that made trouble (and their cause) + if (error_files.size()) error_files += "\n"; + error_files += *iter += " (" + what + ")"; + } + } + // show error message box when some file(s) could not be opened / added + if (error_files.size()) { + Glib::ustring txt = "Could not add the following sample(s):\n" + error_files; + Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR); + msg.run(); + } + } +} + +void MainWindow::on_action_remove_sample() { + if (!file) return; + Glib::RefPtr sel = m_TreeViewSamples.get_selection(); + Gtk::TreeModel::iterator it = sel->get_selected(); + if (it) { + Gtk::TreeModel::Row row = *it; + gig::Group* group = row[m_SamplesModel.m_col_group]; + gig::Sample* sample = row[m_SamplesModel.m_col_sample]; + try { + // remove group or sample from the gig file + if (group) { + file->DeleteGroup(group); + } else if (sample) { + file->DeleteSample(sample); + } + // if sample was just previously added, remove it from the import queue + if (sample) { + for (std::list::iterator iter = m_SampleImportQueue.begin(); iter != m_SampleImportQueue.end(); ++iter) { + if ((*iter).gig_sample == sample) { + m_SampleImportQueue.erase(iter); + break; + } + } + } + // remove respective row(s) from samples tree view + m_refSamplesTreeModel->erase(it); + } catch (RIFF::Exception e) { + Gtk::MessageDialog msg(*this, e.Message.c_str(), false, Gtk::MESSAGE_ERROR); + msg.run(); + } + } +}