• Main Page
  • Classes
  • Files
  • File List
  • File Members

mainwindow.cpp

Go to the documentation of this file.
00001 #include <QCompleter>
00002 #include <QtConcurrentRun>
00003 #include <QDirModel>
00004 #include <QTextStream>
00005 #include <QMessageBox>
00006 #include <QFileDialog>
00007 #include <QTime>
00008 #include <QPixmap>
00009 #include <QWhatsThis>
00010 #include "mainwindow.hpp"
00011 #include <iostream>
00012 #include <cassert>
00013 
00014 namespace
00015 {
00016     void setCombo(QComboBox* combo, int value)
00017     {
00018         for (int index = 0; index < combo->count(); ++index)
00019             if (combo->itemData(index) == value)
00020                 combo->setCurrentIndex(index);
00021     }
00022 }
00023 
00024 MainWindow::MainWindow()
00025 {
00026     ui.setupUi(this);
00027 
00028     QCompleter* completer = new QCompleter(ui.locationEdit);
00029     completer->setModel(new QDirModel(completer));
00030     ui.locationEdit->setCompleter(completer);
00031     ui.speclocEdit->setCompleter(completer);
00032 
00033     resetSoundfile();
00034     connect(ui.locationEdit, SIGNAL(editingFinished()),
00035             this, SLOT(loadSoundfile()));
00036     connect(ui.locationButton, SIGNAL(clicked()),
00037             this, SLOT(chooseSoundfile()));
00038 
00039     connect(ui.paletteButton, SIGNAL(clicked()), this, SLOT(choosePalette()));
00040 
00041     connect(ui.specSaveAsButton, SIGNAL(clicked()), this, SLOT(saveImage()));
00042     connect(ui.speclocButton, SIGNAL(clicked()), this, SLOT(chooseImage()));
00043     connect(ui.makeButton, SIGNAL(clicked()), this, SLOT(makeSpectrogram()));
00044 
00045     //connect(ui.soundSaveAsButton,SIGNAL(clicked()),this, SLOT(saveSoundfile()));
00046     connect(ui.makeSoundButton, SIGNAL(clicked()), this, SLOT(makeSound()));
00047 
00048     ui.intensityCombo->addItem("logarithmic", (int)SCALE_LOGARITHMIC);
00049     ui.intensityCombo->addItem("linear", (int)SCALE_LINEAR);
00050 
00051     ui.frequencyCombo->addItem("logarithmic", (int)SCALE_LOGARITHMIC);
00052     ui.frequencyCombo->addItem("linear", (int)SCALE_LINEAR);
00053     connect(ui.frequencyCombo, SIGNAL(currentIndexChanged(int)),
00054             this, SLOT(setFilterUnits(int)));
00055 
00056     ui.windowCombo->addItem("Hann", (int)WINDOW_HANN);
00057     ui.windowCombo->addItem("Blackman", (int)WINDOW_BLACKMAN);
00058     ui.windowCombo->addItem("Triangular", (int)WINDOW_TRIANGULAR);
00059     ui.windowCombo->addItem("Rectangular (none)", (int)WINDOW_RECTANGULAR);
00060 
00061     ui.syntCombo->addItem("sine", (int)SYNTHESIS_SINE);
00062     ui.syntCombo->addItem("noise", (int)SYNTHESIS_NOISE);
00063 
00064     ui.brightCombo->addItem("none", (int)BRIGHT_NONE);
00065     ui.brightCombo->addItem("square root", (int)BRIGHT_SQRT);
00066 
00067     spectrogram = new Spectrogram(this);
00068     connect(ui.cancelButton, SIGNAL(clicked()), spectrogram, SLOT(cancel()));
00069     connect(spectrogram, SIGNAL(progress(int)),
00070             ui.specProgress, SLOT(setValue(int)));
00071     connect(spectrogram, SIGNAL(status(const QString&)),
00072             ui.specStatus, SLOT(setText(const QString&)));
00073     setValues();
00074 
00075     image_watcher = new QFutureWatcher<QImage>(this);
00076     connect(image_watcher, SIGNAL(finished()), this, SLOT(newSpectrogram()));
00077     sound_watcher = new QFutureWatcher<real_vec>(this);
00078     connect(sound_watcher, SIGNAL(finished()), this, SLOT(newSound()));
00079 
00080     ui.lengthEdit->setDisplayFormat("hh:mm:ss");
00081 
00082     idleState();
00083 }
00084 
00085 void MainWindow::resetSoundfile()
00086 {
00087     soundfile.reset();
00088     ui.lengthEdit->setTime(QTime(0,0,0));
00089     ui.channelsEdit->setText("0");
00090     ui.channelSpin->setMaximum(0);
00091     ui.samplerateSpin->setValue(0);
00092 }
00093 
00094 void MainWindow::loadSoundfile()
00095 {
00096     const QString& filename = ui.locationEdit->text();
00097     if (filename.isEmpty())
00098         return;
00099     soundfile.load(filename);
00100     if (!soundfileOk())
00101     {
00102         QString error;
00103         QTextStream serror(&error);
00104         serror << "The specified file is not readable or not supported.";
00105         if (!soundfile.error().isEmpty())
00106             serror << "\n\n" << soundfile.error();
00107         QMessageBox::warning(this, "Invalid file", error);
00108         return;
00109     }
00110     updateSoundfile();
00111 }
00112 
00113 void MainWindow::updateSoundfile()
00114 {
00115     if (soundfileOk())
00116     {
00117         ui.lengthEdit->setTime(QTime().addSecs((int)soundfile.data().length()));
00118         ui.channelSpin->setMinimum(1);
00119         ui.channelSpin->setMaximum(soundfile.data().channels());
00120         ui.channelsEdit->setText(QString::number(soundfile.data().channels()));
00121         ui.samplerateSpin->setValue(soundfile.data().samplerate());
00122     }
00123     else
00124         resetSoundfile();
00125 }
00126 
00127 bool MainWindow::soundfileOk()
00128 {
00129     return soundfile.valid();
00130 }
00131 
00132 void MainWindow::choosePalette()
00133 {
00134     QString filename = QFileDialog::getOpenFileName(this, 
00135             "Choose the palette image", ".",
00136             "Images (*.png *.jpg *.bmp *.gif);;All files (*.*)");
00137     if (filename == NULL)
00138         return;
00139     QImage img(filename);
00140     if (img.isNull())
00141     {
00142         QMessageBox::warning(this, "Invalid image", 
00143                 "The picture format was not recognised.");
00144         return;
00145     }
00146     spectrogram->palette = Palette(img);
00147     updatePalette();
00148 }
00149 
00150 void MainWindow::chooseSoundfile()
00151 {
00152     QString filename = QFileDialog::getOpenFileName(this, 
00153             "Choose the sound file", ".",
00154             "Sound files (*.wav *.mp3 *.ogg *.flac);;All files (*.*)");
00155     if (filename == NULL)
00156         return;
00157     ui.locationEdit->setText(filename);
00158     loadSoundfile();
00159 }
00160 
00161 void MainWindow::updatePalette()
00162 {
00163     ui.paletteLabel->setPixmap(spectrogram->palette.preview(
00164                 spectrogram->palette.numColors(), ui.paletteLabel->height()));
00165 }
00166 
00167 void MainWindow::saveImage()
00168 {
00169     if (image.isNull())
00170     {
00171         QMessageBox::warning(this, "Couldn't save file",
00172                 "There is nothing to save yet.");
00173         return;
00174     }
00175     QString filename = 
00176         QFileDialog::getSaveFileName(this, "Save spectrogram",
00177             "spectrogram.png", "Images (*.png *.xpm)");
00178     if (filename == NULL)
00179         return;
00180     if (filename.endsWith(".jpg") || filename.endsWith(".JPG"))
00181     {
00182         QMessageBox::warning(this, "Couldn't save file",
00183                 "JPG is not supported for writing.  As a lossy compression format, it is a poor choice for spectrograms anyway.");
00184         return;
00185     }
00186     if (!filename.contains("."))
00187         filename.append(".png");
00188     const bool worked = image.save(filename);
00189     if (!worked)
00190     {
00191         QMessageBox::warning(this, "Couldn't save file",
00192                 "The file could not be saved at the specified location, or you specified a not supported format extension.");
00193         return;
00194     }
00195     ui.speclocEdit->setText(filename);
00196 }
00197 
00198 void MainWindow::makeSpectrogram()
00199 {
00200     if (!soundfileOk())
00201     {
00202         QMessageBox::warning(this, "No sound file",
00203                 "Choose a valid sound file first.");
00204         return;
00205     }
00206 
00207     loadValues();
00208 
00209     if (!checkAnalysisValues())
00210         return;
00211 
00212     workingState();
00213 
00214     const int channelidx = ui.channelSpin->value()-1;
00215     ui.specStatus->setText("Loading sound file");
00216     qApp->processEvents();
00217     real_vec signal = soundfile.read_channel(channelidx);
00218     if (!signal.size())
00219     {
00220         QMessageBox::warning(this, "Error", "Error reading sound file.");
00221         idleState();
00222         return;
00223     }
00224 
00225     QFuture<QImage> future = QtConcurrent::run(spectrogram,
00226           &Spectrogram::to_image, signal, soundfile.data().samplerate());
00227     image_watcher->setFuture(future);
00228 }
00229 
00230 void MainWindow::loadValues()
00231 {
00232     spectrogram->bandwidth = ui.bandwidthSpin->value();
00233     spectrogram->basefreq = ui.basefreqSpin->value();
00234     spectrogram->maxfreq = ui.maxfreqSpin->value();
00235     spectrogram->overlap = ui.overlapSpin->value()/100;
00236     spectrogram->pixpersec = ui.ppsSpin->value();
00237     spectrogram->window = (Window)ui.windowCombo->
00238         itemData(ui.windowCombo->currentIndex()).toInt();
00239     spectrogram->frequency_axis = (AxisScale)ui.frequencyCombo->
00240         itemData(ui.frequencyCombo->currentIndex()).toInt();
00241     spectrogram->intensity_axis = (AxisScale)ui.intensityCombo->
00242         itemData(ui.intensityCombo->currentIndex()).toInt();
00243     spectrogram->correction = (BrightCorrection)ui.brightCombo->
00244         itemData(ui.brightCombo->currentIndex()).toInt();
00245 }
00246 
00247 void MainWindow::newSpectrogram()
00248 {
00249     if (!image_watcher->future().result().isNull()) // cancelled?
00250     {
00251         image = image_watcher->future().result();
00252         ui.speclocEdit->setText("unsaved");
00253         updateImage();
00254     }
00255     idleState();
00256 }
00257 
00258 void MainWindow::workingState()
00259 {
00260     ui.specProgress->setValue(0);
00261     ui.specStatus->setText("Idle");
00262     ui.cancelButton->setEnabled(true);
00263     ui.makeButton->setEnabled(false);
00264     ui.makeSoundButton->setEnabled(false);
00265     ui.speclocButton->setEnabled(false);
00266     ui.paletteButton->setEnabled(false);
00267     ui.locationButton->setEnabled(false);
00268 }
00269 
00270 void MainWindow::idleState()
00271 {
00272     ui.specProgress->setValue(0);
00273     ui.specStatus->setText("Idle");
00274     ui.cancelButton->setEnabled(false);
00275     ui.makeButton->setEnabled(true);
00276     ui.makeSoundButton->setEnabled(true);
00277     ui.speclocButton->setEnabled(true);
00278     ui.paletteButton->setEnabled(true);
00279     ui.locationButton->setEnabled(true);
00280 }
00281 
00282 void MainWindow::setFilterUnits(int index)
00283 {
00284     AxisScale scale = (AxisScale)ui.frequencyCombo->itemData(index).toInt();
00285     switch (scale)
00286     {
00287         case SCALE_LINEAR:
00288             ui.bandwidthSpin->setSuffix(" Hz");
00289             break;
00290         case SCALE_LOGARITHMIC:
00291             ui.bandwidthSpin->setSuffix(" cents");
00292             break;
00293     }
00294 }
00295 
00296 void MainWindow::chooseImage()
00297 {
00298     QString filename = QFileDialog::getOpenFileName(this, 
00299             "Choose the spectrogram", ".",
00300             "Images (*.png *.jpg *.bmp *.gif);;All files (*.*)");
00301     if (filename == NULL)
00302         return;
00303     ui.speclocEdit->setText(filename);
00304     loadImage();
00305 }
00306 
00307 bool MainWindow::imageOk()
00308 {
00309     return !image.isNull();
00310 }
00311 
00312 void MainWindow::loadImage()
00313 {
00314     QString filename = ui.speclocEdit->text();
00315     if (filename.isEmpty())
00316         return;
00317     image.load(filename);
00318     if (!imageOk())
00319     {
00320         QMessageBox::warning(this, "Invalid file", 
00321                 "The specified file is not readable or not supported.");
00322         return;
00323     }
00324     QString params = image.text("Spectrogram");
00325     if (!params.isNull())
00326     {
00327         spectrogram->deserialize(params);
00328         setValues();
00329     }
00330 
00331     updateImage();
00332 }
00333 
00334 void MainWindow::resetImage()
00335 {
00336     ui.speclocEdit->setText("");
00337     ui.sizeEdit->setText("");
00338     ui.spectrogramLabel->setText("");
00339 }
00340 
00341 void MainWindow::updateImage()
00342 {
00343     if (imageOk())
00344     {
00345         if (image.width() > 30000)
00346             ui.spectrogramLabel->setText("Image too large to preview");
00347         else
00348             ui.spectrogramLabel->setPixmap(QPixmap::fromImage(image));
00349         QString sizetext_;
00350         QTextStream sizetext(&sizetext_);
00351         sizetext << image.width() << "x" << image.height() << " px";
00352         ui.sizeEdit->setText(sizetext_);
00353     }
00354     else
00355         resetImage();
00356 }
00357 
00358 void MainWindow::saveSoundfile(const real_vec& signal)
00359 {
00360     QString filename;
00361     while (filename.isNull())
00362     {
00363         filename = QFileDialog::getSaveFileName(this, "Save sound",
00364                 "synt.wav", "Sound (*.wav *.ogg *.flac)");
00365         QMessageBox msg;
00366         msg.setText("If you don't save the sound, it will be discarded.");
00367         msg.setIcon(QMessageBox::Warning);
00368         msg.setStandardButtons(QMessageBox::Discard|QMessageBox::Save);
00369         msg.setDefaultButton(QMessageBox::Discard);
00370         msg.setEscapeButton(QMessageBox::Discard);
00371         if (filename.isNull() && msg.exec() == QMessageBox::Discard)
00372             return;
00373     }
00374     const int samplerate = 44100;
00375     //const int samplerate = ui.samplerateSpin->value();
00376     Soundfile::writeSound(filename, signal, samplerate);
00377     ui.locationEdit->setText(filename);
00378 }
00379 
00380 void MainWindow::makeSound()
00381 {
00382     if (!imageOk())
00383     {
00384         QMessageBox::warning(this, "No spectrogram",
00385                 "Choose or generate a spectrogram first.");
00386         return;
00387     }
00388 
00389     loadValues();
00390     if (!checkSynthesisValues())
00391         return;
00392 
00393     workingState();
00394     SynthesisType type = (SynthesisType)ui.syntCombo->
00395         itemData(ui.syntCombo->currentIndex()).toInt();
00396     //const int samplerate = ui.samplerateSpin->value();
00397     const int samplerate = 44100;
00398     QFuture<real_vec> future = QtConcurrent::run(spectrogram,
00399            &Spectrogram::synthetize, image, samplerate, type);
00400     sound_watcher->setFuture(future);
00401 }
00402 
00403 void MainWindow::newSound()
00404 {
00405     if (sound_watcher->future().result().size()) // cancelled?
00406     {
00407         saveSoundfile(sound_watcher->future().result());
00408         loadSoundfile();
00409     }
00410 
00411     idleState();
00412 }
00413 
00414 void MainWindow::setValues()
00415 {
00416     ui.bandwidthSpin->setValue((int)spectrogram->bandwidth);
00417     ui.basefreqSpin->setValue(spectrogram->basefreq);
00418     ui.maxfreqSpin->setValue(spectrogram->maxfreq);
00419     ui.overlapSpin->setValue(spectrogram->overlap*100);
00420     ui.ppsSpin->setValue((int)spectrogram->pixpersec);
00421     setCombo(ui.windowCombo, spectrogram->window);
00422     setCombo(ui.intensityCombo, spectrogram->intensity_axis);
00423     setCombo(ui.frequencyCombo, spectrogram->frequency_axis);
00424     setCombo(ui.brightCombo, spectrogram->correction);
00425     updatePalette();
00426 }
00427 
00428 bool MainWindow::checkAnalysisValues()
00429 {
00430     QStringList errors;
00431     if (spectrogram->maxfreq > soundfile.data().samplerate()/2)
00432     {
00433         errors.append("Maximum frequency of the spectrogram has to be at most half the sampling frequency (aka. Nyquist frequency) of the sound file.  It will be changed automatically if you continue.");
00434         spectrogram->maxfreq = soundfile.data().samplerate()/2;
00435     }
00436     if (spectrogram->frequency_axis == SCALE_LOGARITHMIC &&
00437             spectrogram->basefreq == 0 )
00438     {
00439         errors.append("Base frequency of a logarithmic spectrogram has to be larger than zero.  It will be set to 27.5 hz.");
00440         spectrogram->basefreq = 27.5;
00441     }
00442     if (spectrogram->window != WINDOW_RECTANGULAR && spectrogram->overlap < 0.4)
00443     {
00444         errors.append("The specified overlap is likely insufficient for use with the selected window function.");
00445     }
00446     const size_t size = soundfile.data().length()*spectrogram->pixpersec;
00447     if (size > 30000)
00448     {
00449         errors.append(QString());
00450         QTextStream(&errors[errors.size()-1]) << "The resulting spectrogram will be very large (" << size << " px), you may have problems viewing it.  Try lowering the Pixels per second value or using a shorter sound.";
00451     }
00452 
00453     return confirmWarnings(errors);
00454 }
00455 
00456 bool MainWindow::confirmWarnings(const QStringList& errors)
00457 {
00458     if (errors.size())
00459     {
00460         QMessageBox msg;
00461         msg.setWindowTitle("Please note...");
00462         msg.setIcon(QMessageBox::Warning);
00463         msg.setText(errors.join("\n\n"));
00464         msg.setStandardButtons(QMessageBox::Ok|QMessageBox::Abort);
00465         msg.setDefaultButton(QMessageBox::Ok);
00466         msg.setEscapeButton(QMessageBox::Abort);
00467         int res = msg.exec();
00468         if (res == QMessageBox::Ok)
00469             setValues();
00470         else if (res == QMessageBox::Abort)
00471             return false;
00472     }
00473     return true;
00474 }
00475 
00476 bool MainWindow::checkSynthesisValues()
00477 {
00478     QStringList errors;
00479     size_t badcolors = 0;
00480     for (int x = 0; x < image.width(); ++x)
00481         for (int y = 0; y < image.height(); ++y)
00482             if (!spectrogram->palette.has_color(image.pixel(x,y)))
00483                 ++badcolors;
00484     if (badcolors)
00485     {
00486         errors.append(QString());
00487         QTextStream(&errors[errors.size()-1]) << "The spectrogram contains "<< badcolors << (badcolors > 1 ? " pixels":" pixel") <<" whose color is not in the selected palette.  Unknown colors are assumed to be zero intensity.  Synthesis quality will likely be affected.";
00488     }
00489 
00490     return confirmWarnings(errors);
00491 }
00492 

Generated by  doxygen 1.7.1