lbajardsilogic@0: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ lbajardsilogic@0: lbajardsilogic@0: /* lbajardsilogic@0: Sonic Visualiser lbajardsilogic@0: An audio file viewer and annotation editor. lbajardsilogic@0: Centre for Digital Music, Queen Mary, University of London. lbajardsilogic@0: This file copyright 2006 Chris Cannam and QMUL. lbajardsilogic@0: lbajardsilogic@0: This program is free software; you can redistribute it and/or lbajardsilogic@0: modify it under the terms of the GNU General Public License as lbajardsilogic@0: published by the Free Software Foundation; either version 2 of the lbajardsilogic@0: License, or (at your option) any later version. See the file lbajardsilogic@0: COPYING included with this distribution for more information. lbajardsilogic@0: */ lbajardsilogic@0: lbajardsilogic@0: #include "MainWindow.h" lbajardsilogic@0: lbajardsilogic@0: #include "system/System.h" lbajardsilogic@0: #include "system/Init.h" lbajardsilogic@0: #include "base/TempDirectory.h" lbajardsilogic@0: #include "base/PropertyContainer.h" lbajardsilogic@0: #include "base/Preferences.h" lbajardsilogic@0: #include "widgets/TipDialog.h" benoitrigolleau@58: #include "widgets/EasaierStyle.h" lbajardsilogic@0: lbajardsilogic@0: #include lbajardsilogic@0: #include lbajardsilogic@0: #include lbajardsilogic@0: #include lbajardsilogic@0: #include lbajardsilogic@0: #include lbajardsilogic@0: #include lbajardsilogic@0: #include lbajardsilogic@0: #include lbajardsilogic@2: #include benoitrigolleau@58: #include lbajardsilogic@0: lbajardsilogic@0: #include lbajardsilogic@0: #include lbajardsilogic@0: lbajardsilogic@0: /*! \mainpage Sonic Visualiser lbajardsilogic@0: lbajardsilogic@0: \section interesting Summary of interesting classes lbajardsilogic@0: lbajardsilogic@0: - Data models: Model and subclasses, e.g. WaveFileModel lbajardsilogic@0: lbajardsilogic@0: - Graphical layers: Layer and subclasses, displayed on View and its lbajardsilogic@0: subclass widgets. lbajardsilogic@0: lbajardsilogic@0: - Main window class, document class, and file parser: MainWindow, lbajardsilogic@0: Document, SVFileReader lbajardsilogic@0: lbajardsilogic@0: - Turning one model (e.g. audio) into another (e.g. more audio, or a lbajardsilogic@0: curve extracted from it): Transform and subclasses lbajardsilogic@0: lbajardsilogic@0: - Creating the plugins used by transforms: RealTimePluginFactory, lbajardsilogic@0: FeatureExtractionPluginFactory. See also the API documentation for lbajardsilogic@0: Vamp feature extraction plugins at lbajardsilogic@0: http://www.vamp-plugins.org/code-doc/. lbajardsilogic@0: lbajardsilogic@0: - File reading and writing code: AudioFileReader and subclasses, lbajardsilogic@0: WavFileWriter, DataFileReader, SVFileReader lbajardsilogic@0: lbajardsilogic@0: - FFT calculation and cacheing: FFTModel, FFTDataServer lbajardsilogic@0: lbajardsilogic@0: - Widgets that show groups of editable properties: PropertyBox for lbajardsilogic@0: layer properties (contained in a PropertyStack), PluginParameterBox lbajardsilogic@0: for plugins (contained in a PluginParameterDialog) lbajardsilogic@0: lbajardsilogic@0: - Audio playback: AudioCallbackPlaySource and subclasses, lbajardsilogic@0: AudioCallbackPlayTarget and subclasses, AudioGenerator lbajardsilogic@0: lbajardsilogic@0: \section model Data sources: the Model hierarchy lbajardsilogic@0: lbajardsilogic@0: A Model is something containing, or knowing how to obtain, data. lbajardsilogic@0: lbajardsilogic@0: For example, WaveFileModel is a model that knows how to get data lbajardsilogic@0: from an audio file; SparseTimeValueModel is a model containing lbajardsilogic@0: editable "curve" data. lbajardsilogic@0: lbajardsilogic@0: Models typically subclass one of a number of abstract subclasses of lbajardsilogic@0: Model. For example, WaveFileModel subclasses DenseTimeValueModel, lbajardsilogic@0: which describes an interface for models that have a value at each lbajardsilogic@0: time point for a given sampling resolution. (Note that lbajardsilogic@0: WaveFileModel does not actually read the files itself: it uses lbajardsilogic@0: AudioFileReader classes for that. It just makes data from the lbajardsilogic@0: files available in a Model.) SparseTimeValueModel uses the lbajardsilogic@0: SparseModel template class, which provides most of the lbajardsilogic@0: implementation for models that contain a series of points of some lbajardsilogic@0: sort -- also used by NoteModel, TextModel, and lbajardsilogic@0: SparseOneDimensionalModel. lbajardsilogic@0: lbajardsilogic@0: Everything that goes on the screen originates from a model, via a lbajardsilogic@0: layer (see below). The models are contained in a Document object. lbajardsilogic@0: There is no containment hierarchy or ordering of models in the lbajardsilogic@0: document. One model is the main model, which defines the sample lbajardsilogic@0: rate for playback. lbajardsilogic@0: lbajardsilogic@0: A model may also be marked as a "derived" model, which means it was lbajardsilogic@0: generated from another model using some transform (feature lbajardsilogic@0: extraction or effect plugin, etc) -- the idea being that they can lbajardsilogic@0: be re-generated using the same transform if a new source model is lbajardsilogic@0: loaded. lbajardsilogic@0: lbajardsilogic@0: \section layer Things that can display data: the Layer hierarchy lbajardsilogic@0: lbajardsilogic@0: A Layer is something that knows how to draw parts of a model onto a lbajardsilogic@0: timeline. lbajardsilogic@0: lbajardsilogic@0: For example, WaveformLayer is a layer which draws waveforms, based lbajardsilogic@0: on WaveFileModel; TimeValueLayer draws curves, based on lbajardsilogic@0: SparseTimeValueModel; SpectrogramLayer draws spectrograms, based on lbajardsilogic@0: WaveFileModel (via FFTModel). lbajardsilogic@0: lbajardsilogic@0: The most basic functions of a layer are: to draw itself onto a lbajardsilogic@0: Pane, against a timeline on the x axis; and to permit user lbajardsilogic@0: interaction. If you were thinking of adding the capability to lbajardsilogic@0: display a new sort of something, then you would want to add a new lbajardsilogic@0: layer type. (You may also need a new model type, depending on lbajardsilogic@0: whether any existing model can capture the data you need.) lbajardsilogic@0: Depending on the sort of data in question, there are various lbajardsilogic@0: existing layers that might be appropriate to start from -- for lbajardsilogic@0: example, a layer that displays images that the user has imported lbajardsilogic@0: and associated with particular times might have something in common lbajardsilogic@0: with the existing TextLayer which displays pieces of text that are lbajardsilogic@0: associated with particular times. lbajardsilogic@0: lbajardsilogic@0: Although layers are visual objects, they are contained in the lbajardsilogic@0: Document in Sonic Visualiser rather than being managed together lbajardsilogic@0: with display widgets. The Sonic Visualiser file format has lbajardsilogic@0: separate data and layout sections, and the layers are defined in lbajardsilogic@0: the data section and then referred to in the layout section which lbajardsilogic@0: determines which layers may go on which panes (see Pane below). lbajardsilogic@0: lbajardsilogic@0: Once a layer class is defined, some basic data about it needs to be lbajardsilogic@0: set up in the LayerFactory class, and then it will appear in the lbajardsilogic@0: menus and so on on the main window. lbajardsilogic@0: lbajardsilogic@0: \section view Widgets that are used to show layers: The View hierarchy lbajardsilogic@0: lbajardsilogic@0: A View is a widget that displays a stack of layers. The most lbajardsilogic@0: important subclass is Pane, the widget that is used to show most of lbajardsilogic@0: the data in the main window of Sonic Visualiser. lbajardsilogic@0: lbajardsilogic@0: All a pane really does is contain a set of layers and get them to lbajardsilogic@0: render themselves (one on top of the other, with the topmost layer lbajardsilogic@0: being the one that is currently interacted with), cache the lbajardsilogic@0: results, negotiate user interaction with them, and so on. This is lbajardsilogic@0: generally fiddly, if not especially interesting. Panes are lbajardsilogic@0: strictly layout objects and are not stored in the Document class; lbajardsilogic@0: instead the MainWindow contains a PaneStack widget (the widget that lbajardsilogic@0: takes up most of Sonic Visualiser's main window) which contains a lbajardsilogic@0: set of panes stacked vertically. lbajardsilogic@0: lbajardsilogic@0: Another View subclass is Overview, which is the widget that lbajardsilogic@0: contains that green waveform showing the entire file at the bottom lbajardsilogic@0: of the window. lbajardsilogic@0: lbajardsilogic@0: */ lbajardsilogic@197: lbajardsilogic@197: #ifdef QT_NODLL lbajardsilogic@197: #include lbajardsilogic@197: #endif lbajardsilogic@0: lbajardsilogic@0: static QMutex cleanupMutex; lbajardsilogic@0: lbajardsilogic@0: static void lbajardsilogic@0: signalHandler(int /* signal */) lbajardsilogic@0: { lbajardsilogic@0: // Avoid this happening more than once across threads lbajardsilogic@0: lbajardsilogic@0: cleanupMutex.lock(); lbajardsilogic@0: std::cerr << "signalHandler: cleaning up and exiting" << std::endl; lbajardsilogic@0: TempDirectory::getInstance()->cleanup(); ivand_qmul@125: ivand_qmul@125: exit(0); // without releasing mutex lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: class SVApplication : public QApplication lbajardsilogic@0: { lbajardsilogic@0: public: lbajardsilogic@0: SVApplication(int argc, char **argv) : lbajardsilogic@0: QApplication(argc, argv), lbajardsilogic@0: m_mainWindow(0) { } lbajardsilogic@0: virtual ~SVApplication() { } lbajardsilogic@0: lbajardsilogic@0: void setMainWindow(MainWindow *mw) { m_mainWindow = mw; } lbajardsilogic@0: void releaseMainWindow() { m_mainWindow = 0; } lbajardsilogic@0: lbajardsilogic@0: virtual void commitData(QSessionManager &manager) { lbajardsilogic@0: if (!m_mainWindow) return; lbajardsilogic@0: bool mayAskUser = manager.allowsInteraction(); lbajardsilogic@0: bool success = m_mainWindow->commitData(mayAskUser); lbajardsilogic@0: manager.release(); lbajardsilogic@0: if (!success) manager.cancel(); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: protected: lbajardsilogic@0: MainWindow *m_mainWindow; lbajardsilogic@0: }; lbajardsilogic@197: lbajardsilogic@197: #ifdef QT_NODLL lbajardsilogic@197: Q_IMPORT_PLUGIN(qjpeg) lbajardsilogic@197: Q_IMPORT_PLUGIN(qgif) lbajardsilogic@197: Q_IMPORT_PLUGIN(qmng) lbajardsilogic@197: #endif lbajardsilogic@0: benoitrigolleau@217: lbajardsilogic@197: int main(int argc, char **argv) lbajardsilogic@0: { benoitrigolleau@58: lbajardsilogic@0: SVApplication application(argc, argv); benoitrigolleau@217: benoitrigolleau@58: QSplashScreen *splash = new QSplashScreen; benoitrigolleau@58: splash->setPixmap(QPixmap(":icons/splashscreen.png")); benoitrigolleau@58: splash->show(); benoitrigolleau@217: lbajardsilogic@0: QStringList args = application.arguments(); lbajardsilogic@0: lbajardsilogic@0: signal(SIGINT, signalHandler); lbajardsilogic@0: signal(SIGTERM, signalHandler); lbajardsilogic@0: lbajardsilogic@0: #ifndef Q_WS_WIN32 lbajardsilogic@0: signal(SIGHUP, signalHandler); lbajardsilogic@0: signal(SIGQUIT, signalHandler); lbajardsilogic@0: #endif lbajardsilogic@0: lbajardsilogic@62: bool redirectStderr = true; lbajardsilogic@62: lbajardsilogic@62: if (args.contains("-log")) lbajardsilogic@62: { lbajardsilogic@62: redirectStderr = false; lbajardsilogic@62: } lbajardsilogic@62: lbajardsilogic@62: svSystemSpecificInitialisation(redirectStderr); lbajardsilogic@0: lbajardsilogic@0: bool audioOutput = true; lbajardsilogic@0: bool oscSupport = true; lbajardsilogic@0: lbajardsilogic@0: if (args.contains("--help") || args.contains("-h") || args.contains("-?")) { lbajardsilogic@0: std::cerr << QApplication::tr( lbajardsilogic@41: "\nSound Access is a program for viewing and exploring multimedia archives for semantic multimedia analysis and retrieval.\n").arg(argv[0]).toStdString() << std::endl; lbajardsilogic@0: exit(2); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: if (args.contains("--no-audio")) audioOutput = false; lbajardsilogic@0: if (args.contains("--no-osc")) oscSupport = false; lbajardsilogic@0: lbajardsilogic@41: QApplication::setOrganizationName("sound-access"); lbajardsilogic@41: QApplication::setOrganizationDomain("easaier.org"); lbajardsilogic@41: QApplication::setApplicationName("sound-access"); lbajardsilogic@0: QApplication::setWindowIcon(QIcon(":icons/svicon16.png")); benoitrigolleau@58: //QApplication::setStyle(new QPlastiqueStyle); benoitrigolleau@58: QApplication::setStyle(new EasaierStyle); lbajardsilogic@2: lbajardsilogic@0: QString language = QLocale::system().name(); lbajardsilogic@0: lbajardsilogic@0: QTranslator qtTranslator; lbajardsilogic@0: QString qtTrName = QString("qt_%1").arg(language); lbajardsilogic@0: std::cerr << "Loading " << qtTrName.toStdString() << "..." << std::endl; lbajardsilogic@0: qtTranslator.load(qtTrName); lbajardsilogic@0: application.installTranslator(&qtTranslator); lbajardsilogic@0: lbajardsilogic@0: QTranslator svTranslator; lbajardsilogic@41: QString svTrName = QString("sound-access_%1").arg(language); lbajardsilogic@0: std::cerr << "Loading " << svTrName.toStdString() << "..." << std::endl; lbajardsilogic@55: if (svTranslator.load(svTrName, ":/i18n")) lbajardsilogic@55: application.installTranslator(&svTranslator); lbajardsilogic@0: lbajardsilogic@0: // Permit size_t and PropertyName to be used as args in queued signal calls lbajardsilogic@0: qRegisterMetaType("size_t"); lbajardsilogic@0: qRegisterMetaType("PropertyContainer::PropertyName"); lbajardsilogic@0: lbajardsilogic@0: MainWindow gui(audioOutput, oscSupport); lbajardsilogic@0: application.setMainWindow(&gui); lbajardsilogic@0: lbajardsilogic@0: QDesktopWidget *desktop = QApplication::desktop(); lbajardsilogic@0: QRect available = desktop->availableGeometry(); lbajardsilogic@0: lbajardsilogic@0: int width = available.width() * 2 / 3; lbajardsilogic@0: int height = available.height() / 2; lbajardsilogic@0: if (height < 450) height = available.height() * 2 / 3; lbajardsilogic@0: if (width > height * 2) width = height * 2; lbajardsilogic@0: lbajardsilogic@0: QSettings settings; lbajardsilogic@0: settings.beginGroup("MainWindow"); lbajardsilogic@0: QSize size = settings.value("size", QSize(width, height)).toSize(); lbajardsilogic@0: gui.resize(size); lbajardsilogic@0: if (settings.contains("position")) { lbajardsilogic@0: gui.move(settings.value("position").toPoint()); lbajardsilogic@0: } lbajardsilogic@0: settings.endGroup(); lbajardsilogic@0: benoitrigolleau@58: //delay(5000); benoitrigolleau@58: lbajardsilogic@0: gui.show(); benoitrigolleau@58: splash->finish(&gui); benoitrigolleau@58: delete splash; lbajardsilogic@0: lbajardsilogic@0: bool haveSession = false; lbajardsilogic@0: bool haveMainModel = false; lbajardsilogic@0: lbajardsilogic@0: for (QStringList::iterator i = args.begin(); i != args.end(); ++i) { lbajardsilogic@0: lbajardsilogic@0: MainWindow::FileOpenStatus status = MainWindow::FileOpenFailed; lbajardsilogic@0: lbajardsilogic@0: if (i == args.begin()) continue; lbajardsilogic@0: if (i->startsWith('-')) continue; lbajardsilogic@0: lbajardsilogic@0: if (i->startsWith("http:") || i->startsWith("ftp:")) { lbajardsilogic@0: status = gui.openURL(QUrl(*i)); lbajardsilogic@0: continue; lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: QString path = *i; lbajardsilogic@0: lbajardsilogic@0: if (path.endsWith("sv")) { lbajardsilogic@0: if (!haveSession) { lbajardsilogic@0: status = gui.openSessionFile(path); lbajardsilogic@0: if (status == MainWindow::FileOpenSucceeded) { lbajardsilogic@0: haveSession = true; lbajardsilogic@0: haveMainModel = true; lbajardsilogic@0: } lbajardsilogic@0: } else { lbajardsilogic@0: std::cerr << "WARNING: Ignoring additional session file argument \"" << path.toStdString() << "\"" << std::endl; lbajardsilogic@0: status = MainWindow::FileOpenSucceeded; lbajardsilogic@0: } lbajardsilogic@0: } lbajardsilogic@0: if (status != MainWindow::FileOpenSucceeded) { lbajardsilogic@0: if (!haveMainModel) { lbajardsilogic@0: status = gui.openSomeFile(path, MainWindow::ReplaceMainModel); lbajardsilogic@0: if (status == MainWindow::FileOpenSucceeded) haveMainModel = true; lbajardsilogic@0: } else { lbajardsilogic@0: status = gui.openSomeFile(path, MainWindow::CreateAdditionalModel); lbajardsilogic@0: } lbajardsilogic@0: } lbajardsilogic@0: if (status == MainWindow::FileOpenFailed) { lbajardsilogic@0: QMessageBox::critical lbajardsilogic@0: (&gui, QMessageBox::tr("Failed to open file"), lbajardsilogic@0: QMessageBox::tr("File \"%1\" could not be opened").arg(path)); lbajardsilogic@0: } lbajardsilogic@0: } lbajardsilogic@0: /* lbajardsilogic@0: TipDialog tipDialog; lbajardsilogic@0: if (tipDialog.isOK()) { lbajardsilogic@0: tipDialog.exec(); lbajardsilogic@0: } lbajardsilogic@0: */ lbajardsilogic@0: int rv = application.exec(); lbajardsilogic@0: // std::cerr << "application.exec() returned " << rv << std::endl; ivand_qmul@125: lbajardsilogic@0: cleanupMutex.lock(); lbajardsilogic@0: TempDirectory::getInstance()->cleanup(); lbajardsilogic@0: application.releaseMainWindow(); lbajardsilogic@0: lbajardsilogic@0: return rv; lbajardsilogic@0: }