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
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())
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
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
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())
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