Import fmit upstream version 0.97.6
[fmit.git] / src / CustomInstrumentTunerForm.cpp
1 // Copyright 2004 "Gilles Degottex"
2
3 // This file is part of "fmit"
4
5 // "fmit" is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version.
9 //
10 // "fmit" is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
19 #include "CustomInstrumentTunerForm.h"
20
21 #include <cassert>
22 #include <iostream>
23 using namespace std;
24 #include <qstring.h>
25 #include <qaction.h>
26 #include <qlabel.h>
27 #include <qgl.h>
28 #include <qlayout.h>
29 #include <qlcdnumber.h>
30 #include <qdial.h>
31 #include <qspinbox.h>
32 #include <qcombobox.h>
33 #include <qsplitter.h>
34 #include <qprogressbar.h>
35 #include <qmessagebox.h>
36 #include <qmenubar.h>
37 #include <qpushbutton.h>
38 #include <qcheckbox.h>
39 #include <qgroupbox.h>
40 #include <qbuttongroup.h>
41 #include <qlineedit.h>
42 #include <qstatusbar.h>
43 #include <qdatetime.h>
44 #include <qradiobutton.h>
45
46 #include <qtextedit.h>
47 #include <qpushbutton.h>
48
49 #include <Music/Convolution.h>
50 using namespace Music;
51 #include "modules/View.h"
52
53 CustomInstrumentTunerForm::CustomInstrumentTunerForm()
54 : m_capture_thread("fmit")
55 , m_timer_refresh(this, "m_timer_refresh")
56 , m_algo_combedfft(NULL)
57 , m_range_filter(&m_dummy_range_filter)
58 , m_quantizer(&m_latency_quantizer)
59 , m_settings("gillesdegottex.ch", "fmit", "009700")     // not necessarily equal to the soft version
60 {
61         View::s_settings = &m_settings;
62         m_settings.add(m_config_form.ui_chkFullScreen);
63         m_settings.add(m_config_form.ui_chkAutoSaveOnExit);
64         m_settings.add(m_config_form.ui_chkShowA4Offset);
65
66         m_settings.add(m_config_form.ui_cbTonality);
67         m_settings.add(m_config_form.ui_cbNotesName);
68         m_settings.add(ui_spinAFreq);
69         m_settings.add(ui_spinA3Offset);
70
71         m_settings.add(m_config_form.ui_chkAutoDetect);
72 #ifdef CAPTURE_JACK
73         m_settings.add(m_config_form.ui_chkJACKAutoConnect);
74         m_settings.add(m_config_form.ui_txtJACKSourcePort);
75 #endif
76 #ifdef CAPTURE_ALSA
77         m_settings.add(m_config_form.ui_chkALSASamplingRateMax);
78         m_settings.add(m_config_form.ui_spinALSASamplingRate);
79         m_settings.add(m_config_form.ui_chkALSAMixMultipleChannels);
80         m_settings.add(m_config_form.ui_txtALSAPCMName);
81 #endif
82 #ifdef CAPTURE_OSS
83         m_settings.add(m_config_form.ui_chkOSSSamplingRateMax);
84         m_settings.add(m_config_form.ui_spinOSSSamplingRate);
85         m_settings.add(m_config_form.ui_chkOSSMixMultipleChannels);
86         m_settings.add(m_config_form.ui_txtOSSPCMName);
87 #endif
88 #ifdef CAPTURE_PORTAUDIO
89         m_settings.add(m_config_form.ui_chkPortAudioSamplingRateMax);
90         m_settings.add(m_config_form.ui_spinPortAudioSamplingRate);
91         m_settings.add(m_config_form.ui_chkPortAudioMixMultipleChannels);
92 #endif
93
94         m_settings.add(m_config_form.ui_spinRefreshTime);
95         m_settings.add(m_config_form.ui_spinMinHT);
96         m_settings.add(m_config_form.ui_spinMaxHT);
97
98         m_settings.add(m_config_form.ui_grpRangeFiltering);
99         m_settings.add(m_config_form.ui_rdRangeFilteringRectangular);
100         m_settings.add(m_config_form.ui_rdRangeFilteringFIR);
101
102         m_settings.add(m_config_form.ui_spinVolumeTreshold);
103         m_settings.add(m_config_form.ui_spinWindowSizeFactor);
104         m_settings.add(m_config_form.ui_chkAlgoUseSubHarmTresh);
105         m_settings.add(m_config_form.ui_spinCombedFFTAudibilityRatio);
106
107         m_settings.add(m_config_form.up_grpFreqRefinement);
108         m_settings.add(m_config_form.ui_rdUseFreqRefinement);
109         m_settings.add(m_config_form.ui_spinFreqRefinMaxHarm);
110         m_settings.add(m_config_form.ui_rdUseTimeRefinement);
111         m_settings.add(m_config_form.ui_spinTimeRefinMaxPeriod);
112
113         m_settings.add(m_config_form.ui_grpQuantizer);
114         m_settings.add(m_config_form.ui_spinErrorLatency);
115
116         m_algo_combedfft = new CombedFT();
117
118         for(int i=0; i<m_capture_thread.getTransports().size(); i++)
119                 m_config_form.ui_cbTransports->insertItem(m_capture_thread.getTransports()[i]->getName());
120
121         if(m_capture_thread.getTransports().empty())
122                 QMessageBox::critical(this, "Error", "Please compile me with a capture system ...");
123         if(m_capture_thread.getTransports().size()==1)
124         {
125                 m_config_form.ui_lblSelectedCaptureSystem->hide();
126                 m_config_form.ui_btnAutoDetect->hide();
127                 m_config_form.ui_chkAutoDetect->hide();
128                 m_config_form.ui_cbTransports->hide();
129         }
130         m_config_form.ui_grpALSA->hide();
131         m_config_form.ui_grpJACK->hide();
132         m_config_form.ui_grpPortAudio->hide();
133         m_config_form.ui_grpOSS->hide();
134
135         ui_lblA3Offset->hide();
136         ui_spinA3Offset->hide();
137
138         connect(&m_capture_thread, SIGNAL(samplingRateChanged(int)), this, SLOT(samplingRateChanged(int)));
139         connect(&m_capture_thread, SIGNAL(errorRaised(const QString&)), this, SLOT(errorRaised(const QString&)));
140         connect(&m_capture_thread, SIGNAL(transportChanged(const QString&)), this, SLOT(transportChanged(const QString&)));
141
142         connect(&m_latency_quantizer, SIGNAL(noteStarted(double,double)), this, SLOT(noteStarted(double,double)));
143         connect(&m_latency_quantizer, SIGNAL(noteFinished(double,double)), this, SLOT(noteFinished(double,double)));
144         connect(&m_dummy_quantizer, SIGNAL(noteStarted(double,double)), this, SLOT(noteStarted(double,double)));
145         connect(&m_dummy_quantizer, SIGNAL(noteFinished(double,double)), this, SLOT(noteFinished(double,double)));
146
147         m_dialTune = new DialView(centralWidget());
148         ui_dialTuneLayout->addWidget(m_dialTune);
149
150         m_glGraph = new GLGraph("Graph", centralWidget());
151         connect(m_glGraph->setting_show, SIGNAL(toggled(bool)), this, SLOT(update_views()));
152         connect(m_glGraph->setting_spinMaxHeight, SIGNAL(valueChanged(int)), this, SLOT(update_views()));
153         m_glGraph->setting_show->addTo(ui_tbViews);
154         ui_graphLayout->addWidget(m_glGraph);
155
156         m_glErrorHistory = new GLErrorHistory(centralWidget());
157         connect(m_glErrorHistory->setting_show, SIGNAL(toggled(bool)), this, SLOT(update_views()));
158         m_glErrorHistory->setting_show->addTo(ui_tbViews);
159         ui_errorLayout->addWidget(m_glErrorHistory);
160
161         // link scales
162         connect(m_dialTune->setting_spinScale, SIGNAL(valueChanged(int)), m_glErrorHistory->setting_spinScale, SLOT(setValue(int)));
163         connect(m_glErrorHistory->setting_spinScale, SIGNAL(valueChanged(int)), m_dialTune->setting_spinScale, SLOT(setValue(int)));
164         connect(m_dialTune->setting_useCents, SIGNAL(toggled(bool)), m_glErrorHistory->setting_useCents, SLOT(setOn(bool)));
165         connect(m_glErrorHistory->setting_useCents, SIGNAL(toggled(bool)), m_dialTune->setting_useCents, SLOT(setOn(bool)));
166
167         m_glVolumeHistory = new GLVolumeHistory(centralWidget());
168         connect(m_config_form.ui_spinVolumeTreshold, SIGNAL(valueChanged(int)), m_glVolumeHistory, SLOT(setVolumeTreshold(int)));
169         connect(m_glVolumeHistory->setting_show, SIGNAL(toggled(bool)), this, SLOT(update_views()));
170         m_glVolumeHistory->setting_show->addTo(ui_tbViews);
171         ui_volumeLayout->addWidget(m_glVolumeHistory);
172
173         // link keep settings
174         connect(ui_btnKeepErrorHistory, SIGNAL(toggled(bool)), m_glErrorHistory->setting_keep, SLOT(setOn(bool)));
175         connect(m_glErrorHistory->setting_keep, SIGNAL(toggled(bool)), m_glVolumeHistory->setting_keep, SLOT(setOn(bool)));
176         connect(m_glErrorHistory->setting_keep, SIGNAL(toggled(bool)), ui_btnKeepErrorHistory, SLOT(setOn(bool)));
177
178         m_glSample = new GLSample(centralWidget());
179         connect(m_glSample->setting_show, SIGNAL(toggled(bool)), this, SLOT(update_views()));
180         m_glSample->setting_show->addTo(ui_tbViews);
181         ui_sampleLayout->addWidget(m_glSample);
182
183         m_glFreqStruct = new GLFreqStruct(centralWidget());
184         connect(m_glFreqStruct->setting_show, SIGNAL(toggled(bool)), this, SLOT(update_views()));
185         m_glFreqStruct->setting_show->addTo(ui_tbViews);
186         ui_formantsLayout->addWidget(m_glFreqStruct);
187
188         m_glFT = new GLFT(centralWidget());
189         connect(m_glFT->setting_show, SIGNAL(toggled(bool)), this, SLOT(update_views()));
190         m_glFT->setting_show->addTo(ui_tbViews);
191         ui_FT->addWidget(m_glFT);
192
193         m_microtonalView = new MicrotonalView(centralWidget());
194         connect(m_microtonalView->setting_show, SIGNAL(toggled(bool)), this, SLOT(update_views()));
195         connect(m_microtonalView, SIGNAL(tuningFreqChanged(float)), this, SLOT(tuningFreqChanged(float)));
196         m_microtonalView->setting_show->addTo(ui_tbViews);
197         ui_microtonalLayout->addWidget(m_microtonalView);
198
199         m_glStatistics = new GLStatistics(centralWidget());
200         connect(m_glStatistics->setting_show, SIGNAL(toggled(bool)), this, SLOT(update_views()));
201         m_glStatistics->setting_show->addTo(ui_tbViews);
202         ui_microtonalLayout->addWidget(m_glStatistics);
203
204         connect(m_dialTune->setting_spinScale, SIGNAL(valueChanged(int)), m_glStatistics->setting_spinScale, SLOT(setValue(int)));
205         connect(m_glStatistics->setting_spinScale, SIGNAL(valueChanged(int)), m_dialTune->setting_spinScale, SLOT(setValue(int)));
206         connect(m_dialTune->setting_useCents, SIGNAL(toggled(bool)), m_glStatistics->setting_useCents, SLOT(setOn(bool)));
207         connect(m_glStatistics->setting_useCents, SIGNAL(toggled(bool)), m_dialTune->setting_useCents, SLOT(setOn(bool)));
208
209         connect(m_dialTune->setting_showTolerance, SIGNAL(toggled(bool)), m_glStatistics->setting_showTolerance, SLOT(setOn(bool)));
210         connect(m_glStatistics->setting_showTolerance, SIGNAL(toggled(bool)), m_dialTune->setting_showTolerance, SLOT(setOn(bool)));
211
212         connect(m_config_form.buttonOk, SIGNAL(clicked()), this, SLOT(configure_ok()));
213         connect(m_config_form.ui_btnRestoreFactorySettings, SIGNAL(clicked()), this, SLOT(restoreFactorySettings()));
214         connect(m_config_form.ui_spinMinHT, SIGNAL(valueChanged(int)), this, SLOT(noteRangeChanged()));
215         connect(m_config_form.ui_spinMaxHT, SIGNAL(valueChanged(int)), this, SLOT(noteRangeChanged()));
216         connect(m_config_form.ui_cbTonality, SIGNAL(highlighted(int)), this, SLOT(noteRangeChanged()));
217         connect(m_config_form.ui_cbNotesName, SIGNAL(highlighted(int)), this, SLOT(noteRangeChanged()));
218         connect(m_config_form.ui_btnAutoDetect, SIGNAL(clicked()), this, SLOT(autoDetectTransport()));
219         connect(m_config_form.ui_cbTransports, SIGNAL(activated(const QString&)), this, SLOT(selectTransport(const QString&)));
220         connect(m_config_form.ui_chkALSAMixMultipleChannels, SIGNAL(toggled(bool)), &m_capture_thread, SLOT(setMixMultipleChannels(bool)));
221         connect(m_config_form.ui_chkOSSMixMultipleChannels, SIGNAL(toggled(bool)), &m_capture_thread, SLOT(setMixMultipleChannels(bool)));
222         connect(m_config_form.ui_chkPortAudioMixMultipleChannels, SIGNAL(toggled(bool)), &m_capture_thread, SLOT(setMixMultipleChannels(bool)));
223
224         loadSettings();
225
226         if(m_config_form.ui_chkAutoDetect->isChecked())
227                 m_capture_thread.autoDetectTransport();
228
229         configure_ok();
230
231         if(m_config_form.ui_chkFullScreen->isChecked())
232                 toggleFullScreen();
233
234         m_time_refresh_views.start();
235         m_time_refresh.start();
236         m_time.start();
237         connect((QObject*)&m_timer_refresh, SIGNAL(timeout()), this, SLOT(refresh()));
238         m_timer_refresh.start(m_config_form.ui_spinRefreshTime->value());
239 }
240
241 void CustomInstrumentTunerForm::transportChanged(const QString& name)
242 {
243         selectTransport(name);
244
245         if(m_capture_thread.getCurrentTransportIndex()!=m_config_form.ui_cbTransports->currentItem())
246                 m_config_form.ui_cbTransports->setCurrentItem(m_capture_thread.getCurrentTransportIndex());
247 }
248 void CustomInstrumentTunerForm::selectTransport(const QString& name)
249 {
250         m_config_form.ui_grpALSA->hide();
251         m_config_form.ui_grpJACK->hide();
252         m_config_form.ui_grpPortAudio->hide();
253         m_config_form.ui_grpOSS->hide();
254
255         if(name=="ALSA")                        m_config_form.ui_grpALSA->show();
256         else if(name=="JACK")           m_config_form.ui_grpJACK->show();
257         else if(name=="PortAudio")      m_config_form.ui_grpPortAudio->show();
258         else if(name=="OSS")            m_config_form.ui_grpOSS->show();
259 }
260 void CustomInstrumentTunerForm::autoDetectTransport()
261 {
262         m_capture_thread.autoDetectTransport();
263
264         // here transportChanged will be called
265 }
266
267 void CustomInstrumentTunerForm::toggleFullScreen()
268 {
269         static bool fs = true;
270         if(fs)
271         {
272                 m_config_form.ui_chkFullScreen->setChecked(true);
273                 showFullScreen();
274         }
275         else
276         {
277                 m_config_form.ui_chkFullScreen->setChecked(false);
278                 showNormal();
279         }
280         fs = !fs;
281 }
282
283 void CustomInstrumentTunerForm::noteRangeChanged()
284 {
285         //      cerr << "CustomInstrumentTunerForm::noteRangeChanged" << endl;
286
287         m_config_form.ui_txtMinHT->setText(h2n(m_config_form.ui_spinMinHT->value())+" = "+QString::number(h2f(m_config_form.ui_spinMinHT->value()))+" hz");
288         m_config_form.ui_txtMaxHT->setText(h2n(m_config_form.ui_spinMaxHT->value())+" = "+QString::number(h2f(m_config_form.ui_spinMaxHT->value()))+" hz");
289 }
290
291 void CustomInstrumentTunerForm::errorRaised(const QString& error)
292 {
293         //      cerr << "CustomInstrumentTunerForm::errorRaised " << error << endl;
294
295         statusBar()->message(QString("ERROR: ")+error);
296
297         ui_lblSoundStability->setBackgroundColor(QColor(180,74,74));
298 }
299
300 void CustomInstrumentTunerForm::samplingRateChanged(int sampling_rate)
301 {
302 //      cerr << "CustomInstrumentTunerForm::samplingRateChanged " << sampling_rate << endl;
303
304         Music::SetSamplingRate(sampling_rate);
305
306         m_rect_range_filter.reset(int(GetSamplingRate()/h2f(GetSemitoneMin())));
307
308         if(m_config_form.ui_cbTransports->currentText()=="JACK")
309                 m_config_form.ui_lblJACKSamplingRate->setText(QString::number(sampling_rate));
310 }
311
312 void CustomInstrumentTunerForm::ui_spinAFreq_valueChanged(int AFreq)
313 {
314         double A = AFreq;
315         if(m_config_form.ui_chkShowA4Offset->isOn())
316                 A = h2f(ui_spinA3Offset->value()*1/100.0f, A);
317         Music::SetAFreq(A);
318 //      cerr << A << endl;
319 }
320 void CustomInstrumentTunerForm::ui_spinAOffset_valueChanged(int offset)
321 {
322         double A = ui_spinAFreq->value();
323         if(m_config_form.ui_chkShowA4Offset->isOn())
324                 A = h2f(offset*1/100.0f, ui_spinAFreq->value());
325         Music::SetAFreq(A);
326 //      cerr << A << endl;
327 }
328
329 void CustomInstrumentTunerForm::tuningFreqChanged(float freq)
330 {
331         //      cerr << "CustomInstrumentTunerForm::tuningFreqChanged " << freq << endl;
332
333         if(freq==0.0f)
334         {
335                 if(m_compared_freq!=0.0f)
336                 {
337                         ui_txtNoteFreq->display(m_compared_freq);
338                         ui_txtNote->setText(h2n(f2h(m_compared_freq)));
339                 }
340         }
341         else
342         {
343                 m_compared_freq = freq;
344                 ui_txtNoteFreq->display(int(freq*100)/100.0f);
345                 ui_txtNote->setText(m_microtonalView->getTuningNoteName());
346         }
347
348         m_quantizer->reset();
349
350         //      m_dialTune->setError(-10.0f);
351 }
352
353 void CustomInstrumentTunerForm::pause(bool on)
354 {
355         m_capture_thread.togglePause(on);
356
357         if(on)  m_timer_refresh.stop();
358         else    m_timer_refresh.start(m_config_form.ui_spinRefreshTime->value());
359 }
360
361 void CustomInstrumentTunerForm::refresh()
362 {
363         double elapsed_time = m_time_refresh.elapsed();
364         m_time_refresh.start();
365
366         QColor capture_failed_color(180,74,74);
367         QColor prb_color(208,146,0);
368         QColor empty_color(128,128,128);
369         QColor ok_color(83,165,105);
370
371         // 1/{time between refresh} = {nb refresh by seconds}
372         // limit the nb new data by fs/{nb refresh by seconds}
373         // add 1 to {nb refresh by second} to eventualy recover lags
374         int limit = int( m_capture_thread.getSamplingRate() /
375                         (1.0/(m_config_form.ui_spinRefreshTime->value()/1000.0) - 1));
376
377 //      cerr << "REFRESH ";
378
379         m_capture_thread.lock();
380         double max_amplitude = 0.0;
381         int nb_new_data = 0;
382         while(!m_capture_thread.m_values.empty() &&
383                           (m_capture_thread.m_values.size()>m_capture_thread.getPacketSizeSinceLastLock() || nb_new_data<limit))
384         {
385 //              cerr << m_capture_thread.m_values.back() << " ";
386                 double value = (*m_range_filter)(m_capture_thread.m_values.back());
387 //              cerr << value << " ";
388                 m_capture_thread.m_values.pop_back();
389
390                 max_amplitude = max(max_amplitude, fabs(value));
391
392                 m_queue.push_front(value);
393                 if(m_glGraph)   m_glGraph->addValue(value);
394                 if(m_glFT)              m_glFT->buff.push_front(value);
395
396                 nb_new_data++;
397         }
398         m_capture_thread.unlock();
399
400 //      cerr << endl;
401
402         int max_size = max(m_range_filter->getLength(), max(m_glGraph->getLength(), m_algo_combedfft->getMinSize()));
403         while(!m_queue.empty() && int(m_queue.size())>max_size)
404                 m_queue.pop_back();
405
406         // refresh graph data
407         m_glGraph->refreshGraph();      // TODO refresh the view each time ??
408         m_glFT->refreshGraph();
409
410         // ------- Analysis stage -------
411
412         // if something goes wrong in the capture system
413         if(nb_new_data==0 || m_algo_combedfft==NULL
414                    || elapsed_time>8*m_config_form.ui_spinRefreshTime->value())
415                 ui_lblSoundStability->setBackgroundColor(capture_failed_color);
416         else
417         {
418                 m_algo_combedfft->apply(m_queue);
419
420                 double max_component = 20*log10(m_algo_combedfft->getComponentsMax());
421                 ui_pgbVolume->setProgress(100+int(max_component));
422
423                 double freq = 0.0;
424                 if(m_algo_combedfft->hasNoteRecognized())
425                         freq = m_algo_combedfft->getFondamentalFreq();
426
427                 double freq_rel = freq*m_algo_combedfft->m_plan.in.size()/double(GetSamplingRate());
428                 if(freq_rel<1 || freq_rel>(m_algo_combedfft->m_plan.out.size()/2))
429                         freq = 0.0;
430
431                 // frequency refinement
432                 if(freq>0.0 && m_config_form.up_grpFreqRefinement->isChecked())
433                 {
434                         if(m_config_form.ui_rdUseFreqRefinement->isChecked())
435                         {
436                                 freq = FundFreqRefinementOfHarmonicStruct(m_algo_combedfft->m_plan.out, freq, m_config_form.ui_spinFreqRefinMaxHarm->value(), m_algo_combedfft->getZeroPaddingFactor());
437
438                         }
439                         else if(m_config_form.ui_rdUseTimeRefinement->isChecked())
440                         {
441                                 double period = GetAveragePeriodFromApprox(m_queue, int(GetSamplingRate()/freq), m_config_form.ui_spinTimeRefinMaxPeriod->value());
442                                 if(period>0.0)
443                                         freq = GetSamplingRate()/period;
444                         }
445                 }
446
447 //              cerr << "2) test freq=" << m_test_freq <<endl;
448
449                 m_quantizer->quantize(freq);
450
451                 if(!m_quantizer->isPlaying())
452                         ui_lblSoundStability->setBackgroundColor(empty_color);
453                 else
454                 {
455                         if(max_amplitude>=1.0)
456                                 ui_lblSoundStability->setBackgroundColor(prb_color);
457                         else
458                                 ui_lblSoundStability->setBackgroundColor(ok_color);
459
460                         m_freq = m_quantizer->getAverageFrequency();
461                         m_error = f2hf(m_freq, m_compared_freq);
462
463                         // refresh error
464                         m_glErrorHistory->addError(m_error);
465                         m_dialTune->setError(m_error);
466                         m_dialTune->m_avg_error = m_glErrorHistory->m_notes.back().avg_err;
467                         m_dialTune->m_min_error = m_glErrorHistory->m_notes.back().min_err;
468                         m_dialTune->m_max_error = m_glErrorHistory->m_notes.back().max_err;
469                         ui_txtFreq->display(m_freq);
470
471                         // refresh intonal tuning cursor
472                         m_microtonalView->setAFreq(Music::GetAFreq());
473                         m_microtonalView->updateCursor(m_freq);
474
475                         // volume
476                         m_glVolumeHistory->addVolume(max_component);
477
478                         // refresh sample data
479                         refresh_data_sample();
480
481                         // refresh formants data
482                         refresh_data_harmonics();
483
484                         m_glStatistics->addNote(f2h(m_compared_freq), m_error);
485                 }
486         }
487
488         if(m_time_refresh_views.elapsed()>50)   // 20 images/second max
489                 refresh_views();
490 }
491
492 void CustomInstrumentTunerForm::noteStarted(double freq, double dt)
493 {
494 //      cerr << "CustomInstrumentTunerForm::noteStarted " << freq << "," << dt << endl;
495
496         // set the compared freq
497         if(m_microtonalView->setting_show->isOn() && m_microtonalView->hasTuningFreqSelected())
498                 m_compared_freq = m_microtonalView->getTuningFreq();
499         else
500                 m_compared_freq = m_quantizer->getCenterFrequency();    // h2f(f2h(freq));
501
502         if(m_microtonalView->setting_show->isOn() && m_microtonalView->hasTuningFreqSelected())
503         {
504                 ui_txtNoteFreq->display(int(m_microtonalView->getTuningFreq()*100)/100.0);
505                 ui_txtNote->setText(m_microtonalView->getTuningNoteName());
506                 if(m_microtonalView->m_selected_jivalue->is_ratio)
507                 {
508                         m_glErrorHistory->addNote(GLErrorHistory::Note(m_microtonalView->setting_selectedRoot, m_microtonalView->m_selected_jivalue->num, m_microtonalView->m_selected_jivalue->den));
509                         m_glVolumeHistory->addNote(GLVolumeHistory::Note(m_microtonalView->setting_selectedRoot, m_microtonalView->m_selected_jivalue->num, m_microtonalView->m_selected_jivalue->den));
510                 }
511                 else
512                 {
513                         m_glErrorHistory->addNote(GLErrorHistory::Note(m_microtonalView->setting_selectedRoot, m_microtonalView->m_selected_jivalue->cents));
514                         m_glVolumeHistory->addNote(GLVolumeHistory::Note(m_microtonalView->setting_selectedRoot, m_microtonalView->m_selected_jivalue->cents));
515                 }
516         }
517         else
518         {
519                 ui_txtNoteFreq->display(m_compared_freq);
520                 ui_txtNote->setText(h2n(f2h(m_compared_freq)));
521                 m_glErrorHistory->addNote(GLErrorHistory::Note(f2h(m_compared_freq)));
522                 m_glVolumeHistory->addNote(GLVolumeHistory::Note(f2h(m_compared_freq)));
523         }
524 }
525 void CustomInstrumentTunerForm::noteFinished(double freq, double dt)
526 {
527         m_compared_freq = 0.0;
528 //      cerr << "CustomInstrumentTunerForm::noteFinished " << freq << "," << dt << endl;
529 }
530
531 void CustomInstrumentTunerForm::refresh_data_sample()
532 {
533         if(m_freq==0.0f || !m_glSample->setting_show->isOn())
534         {
535                 m_glSample->clear();
536                 return;
537         }
538
539         deque<double> sample;
540         GetWaveSample(m_queue, size_t(m_capture_thread.getSamplingRate()/m_freq), sample);
541         m_glSample->add(m_time.elapsed(), sample);
542 }
543
544 void CustomInstrumentTunerForm::refresh_data_harmonics()
545 {
546         if(!(m_algo_combedfft!=NULL &&
547                         m_freq>0.0f &&
548                         m_glFreqStruct->setting_show->isOn()))
549                 return;
550
551         vector<Harmonic> harms = GetHarmonicStruct(m_algo_combedfft->m_plan.out, m_freq, m_glFreqStruct->m_components.size(), m_algo_combedfft->getZeroPaddingFactor());
552
553         m_glFreqStruct->m_components_max = 0.0;
554         for(int i=0; i<harms.size(); i++)
555         {
556                 if(harms[i].harm_number<m_glFreqStruct->m_components.size())
557                 {
558                         m_glFreqStruct->m_components[harms[i].harm_number-1] = 20*log10(harms[i].mod/0.001);
559
560                         m_glFreqStruct->m_components_max = max(m_glFreqStruct->m_components_max, m_glFreqStruct->m_components[i]);
561                 }
562         }
563 }
564
565 void CustomInstrumentTunerForm::refresh_views()
566 {
567 //      cerr << "CustomInstrumentTunerForm::refresh_views " << endl;
568
569 //      m_dialTune->repaint();
570
571         if(m_glGraph->setting_show->isOn())
572                 m_glGraph->updateGL();
573
574         if(m_glErrorHistory->setting_show->isOn())
575                 m_glErrorHistory->updateGL();
576
577         if(m_glVolumeHistory->setting_show->isOn())
578                 m_glVolumeHistory->updateGL();
579
580         if(m_microtonalView->setting_show->isOn())
581                 m_microtonalView->update();
582
583         if(m_glSample->setting_show->isOn())
584                 m_glSample->updateGL();
585
586         if(m_glFreqStruct->setting_show->isOn())
587                 m_glFreqStruct->updateGL();
588
589         if(m_glFT->setting_show->isOn())
590                 m_glFT->updateGL();
591
592         if(m_glStatistics->setting_show->isOn())
593                 m_glStatistics->updateGL();
594
595         m_time_refresh_views.start();
596 }
597
598 void CustomInstrumentTunerForm::keyPressEvent(QKeyEvent * e)
599 {
600         if(e->key()==Key_F)
601                 toggleFullScreen();
602 }
603
604 void CustomInstrumentTunerForm::resizeEvent(QResizeEvent* e)
605 {
606         update_views();
607
608         InstrumentTunerForm::resizeEvent(e);
609 }
610
611 void CustomInstrumentTunerForm::update_views()
612 {
613         if(     !m_glGraph->setting_show->isOn() &&
614                 !m_glErrorHistory->setting_show->isOn() &&
615                 !m_glVolumeHistory->setting_show->isOn() &&
616                 !m_glSample->setting_show->isOn() &&
617                 !m_glFreqStruct->setting_show->isOn() &&
618                 !m_glFT->setting_show->isOn())
619                         m_dialTune->setMaximumWidth(size().width());
620         else
621                 m_dialTune->setMaximumWidth(ui_rightLayout->minimumSize().width());
622
623         if(m_glGraph->setting_show->isOn() &&
624                         !m_glErrorHistory->setting_show->isOn() &&
625                         !m_glVolumeHistory->setting_show->isOn() &&
626                         !m_glSample->setting_show->isOn() &&
627                         !m_glFreqStruct->setting_show->isOn() &&
628                         !m_glFT->setting_show->isOn())
629                 m_glGraph->setMaximumHeight(size().height());
630         else
631                 m_glGraph->setMaximumHeight(m_glGraph->setting_spinMaxHeight->value());
632
633         if(!m_glErrorHistory->setting_show->isOn() && !m_glVolumeHistory->setting_show->isOn())
634                 ui_btnKeepErrorHistory->hide();
635         else
636                 ui_btnKeepErrorHistory->show();
637 }
638
639 void CustomInstrumentTunerForm::configure()
640 {
641         noteRangeChanged();
642
643         if(m_capture_thread.getCurrentTransportIndex()<m_config_form.ui_cbTransports->count());
644                 m_config_form.ui_cbTransports->setCurrentItem(m_capture_thread.getCurrentTransportIndex());
645
646 #ifdef CAPTURE_JACK
647         // TODO set descr
648         m_config_form.ui_grpJACK->setTitle(m_capture_thread.getTransport("JACK")->getDescription());
649         m_config_form.ui_lblJACKSamplingRate->setText(QString::number(m_capture_thread.getSamplingRate()));
650 #endif
651 #ifdef CAPTURE_PORTAUDIO
652         m_config_form.ui_grpPortAudio->setTitle(m_capture_thread.getTransport("PortAudio")->getDescription());
653         m_config_form.ui_spinPortAudioSamplingRate->setValue(m_capture_thread.getSamplingRate());
654         if(m_capture_thread.getTransport("PortAudio"))
655         {
656                 try
657                 {
658                         PaError err;
659                         err = Pa_Initialize();
660                         if(err != paNoError)
661                                 throw QString("PortAudio: CustomInstrumentTunerForm::configure:Pa_Initialize ")+Pa_GetErrorText(err);
662                         int     numDevices = Pa_GetDeviceCount();
663
664                         int current_index = -1;
665                         m_config_form.ui_cbPortAudioDeviceName->clear();
666                         m_config_form.ui_cbPortAudioDeviceName->insertItem("default");
667                         const PaDeviceInfo* deviceInfo;
668                         for(int i=0; i<numDevices; i++)
669                         {
670                                 deviceInfo = Pa_GetDeviceInfo(i);
671                                 m_config_form.ui_cbPortAudioDeviceName->insertItem(QString(deviceInfo->name));
672                                 if(QString(deviceInfo->name)==m_capture_thread.getTransport("PortAudio")->getSource())
673                                         current_index = i+1;
674                         }
675                         m_config_form.ui_cbPortAudioDeviceName->setCurrentItem(current_index);
676                 }
677                 catch(QString error)
678                 {
679                         cerr << "CustomInstrumentTunerForm: ERROR: " << error << endl;
680                 }
681                 Pa_Terminate();
682         }
683 #endif
684 #ifdef CAPTURE_ALSA
685         m_config_form.ui_grpALSA->setTitle(m_capture_thread.getTransport("ALSA")->getDescription());
686         m_config_form.ui_spinALSASamplingRate->setValue(m_capture_thread.getSamplingRate());
687 #endif
688 #ifdef CAPTURE_OSS
689         m_config_form.ui_grpOSS->setTitle(m_capture_thread.getTransport("OSS")->getDescription());
690         m_config_form.ui_spinOSSSamplingRate->setValue(m_capture_thread.getSamplingRate());
691 #endif
692
693         m_config_form.adjustSize();
694         m_config_form.show();
695 }
696 void CustomInstrumentTunerForm::configure_ok()
697 {
698         if(m_config_form.ui_cbTonality->currentItem()==0)               SetTonality(0);
699         else if(m_config_form.ui_cbTonality->currentItem()==1)  SetTonality(+2);
700         else                                                                                                    SetTonality(-3);
701
702         if(m_config_form.ui_cbNotesName->currentItem()==0)              SetNotesName(LOCAL_ANGLO);
703         else                                                                                                    SetNotesName(LOCAL_LATIN);
704         m_microtonalView->notesNameChanged();
705         m_microtonalView->setAFreq(Music::GetAFreq());
706
707         SetSemitoneBounds(m_config_form.ui_spinMinHT->value(), m_config_form.ui_spinMaxHT->value());
708
709         ui_spinA3Offset->setShown(m_config_form.ui_chkShowA4Offset->isOn());
710         ui_lblA3Offset->setShown(m_config_form.ui_chkShowA4Offset->isOn());
711
712         //      if(m_note!=-1000)
713         //              ui_txtNote->setText(h2n(m_note));
714
715         //      m_dialTune->setError(-10.0f);
716
717 //      cerr << "b" << endl;
718
719         // Capture
720 #ifdef CAPTURE_ALSA
721         if(m_config_form.ui_cbTransports->currentText()=="ALSA")
722         {
723                 m_capture_thread.selectTransport("ALSA");
724                 m_capture_thread.setSource(m_config_form.ui_txtALSAPCMName->text());
725                 if(m_config_form.ui_chkALSASamplingRateMax->isChecked())
726                         m_capture_thread.setSamplingRate(CaptureThread::SAMPLING_RATE_MAX);
727                 else
728                         m_capture_thread.setSamplingRate(m_config_form.ui_spinALSASamplingRate->value());
729         }
730 #endif
731 #ifdef CAPTURE_JACK
732         if(m_config_form.ui_cbTransports->currentText()=="JACK")
733         {
734                 m_capture_thread.selectTransport("JACK");
735                 if(m_config_form.ui_chkJACKAutoConnect->isChecked())
736                         m_capture_thread.setSource(m_config_form.ui_txtJACKSourcePort->text());
737                 else
738                         m_capture_thread.setSource("");
739                 m_config_form.ui_lblJACKSamplingRate->setText(QString::number(m_capture_thread.getSamplingRate()));
740         }
741 #endif
742 #ifdef CAPTURE_PORTAUDIO
743         if(m_config_form.ui_cbTransports->currentText()=="PortAudio")
744         {
745                 m_capture_thread.selectTransport("PortAudio");
746                 m_capture_thread.setSource(m_config_form.ui_cbPortAudioDeviceName->currentText());
747                 if(m_config_form.ui_chkPortAudioSamplingRateMax->isChecked())
748                         m_capture_thread.setSamplingRate(CaptureThread::SAMPLING_RATE_MAX);
749                 else
750                         m_capture_thread.setSamplingRate(m_config_form.ui_spinPortAudioSamplingRate->value());
751         }
752 #endif
753 #ifdef CAPTURE_OSS
754         if(m_config_form.ui_cbTransports->currentText()=="OSS")
755         {
756                 m_capture_thread.selectTransport("OSS");
757                 m_capture_thread.setSource(m_config_form.ui_txtOSSPCMName->text());
758                 if(m_config_form.ui_chkOSSSamplingRateMax->isChecked())
759                         m_capture_thread.setSamplingRate(CaptureThread::SAMPLING_RATE_MAX);
760                 else
761                         m_capture_thread.setSamplingRate(m_config_form.ui_spinOSSSamplingRate->value());
762         }
763 #endif
764         m_timer_refresh.changeInterval(m_config_form.ui_spinRefreshTime->value());
765
766         // Views
767         m_glGraph->m_treshold = pow(10, m_config_form.ui_spinVolumeTreshold->value()/20.0);
768         m_glGraph->clearValues();
769
770 //      cerr << "c" << endl;
771
772         if(m_config_form.ui_grpRangeFiltering->isChecked())
773         {
774                 m_rect_range_filter.reset(int(GetSamplingRate()/h2f(GetSemitoneMin())));
775 //              m_fir_range_filter.setImpulseResponse(fir1_highpass(int(GetSamplingRate()/h2f(GetSemitoneMin())), ));
776
777                 if(m_config_form.ui_rdRangeFilteringRectangular->isChecked())
778                         m_range_filter = &m_rect_range_filter;
779                 else if(m_config_form.ui_rdRangeFilteringFIR->isChecked())
780                         m_range_filter = &m_fir_range_filter;
781         }
782         else
783                 m_range_filter = &m_dummy_range_filter;
784
785         m_algo_combedfft->setWindowFactor(m_config_form.ui_spinWindowSizeFactor->value());
786 //      m_glFT->m_zp_factor = m_config_form.ui_spinWindowSizeFactor->value();
787         m_algo_combedfft->useAudibilityRatio(m_config_form.ui_chkAlgoUseSubHarmTresh->isChecked());
788         m_algo_combedfft->setAudibilityRatio(pow(10, -m_config_form.ui_spinCombedFFTAudibilityRatio->value()/20.0));
789         m_algo_combedfft->setAmplitudeTreshold(pow(10, m_config_form.ui_spinVolumeTreshold->value()/20.0));
790         m_algo_combedfft->setComponentTreshold(pow(10, m_config_form.ui_spinVolumeTreshold->value()/20.0));
791
792 //      cerr << "d" << endl;
793
794         // Quantizers
795         m_quantizer->reset();
796         if(m_config_form.ui_grpQuantizer->isChecked())
797         {
798                 m_latency_quantizer.setLatency(m_config_form.ui_spinErrorLatency->value());
799                 m_quantizer = &m_latency_quantizer;
800         }
801         else
802                 m_quantizer = &m_dummy_quantizer;
803
804 //      cerr << pow(10, -m_config_form.ui_spinCombedFFTAudibilityRatio->value()/20.0) << endl;
805
806         if(!pauseAction->isOn() && !m_capture_thread.isCapturing())
807                 m_capture_thread.startCapture();
808 }
809
810 void CustomInstrumentTunerForm::saveSettings()
811 {
812         m_settings.save();
813         View::saveAll();
814
815         // views
816         m_settings.writeEntry("width", width());
817         m_settings.writeEntry("height", height());
818         m_settings.writeEntry("ui_tbViews", ui_tbViews->isShown());
819         m_settings.writeEntry("ui_tbButtons", ui_tbButtons->isShown());
820
821         // sound capture
822         m_settings.writeEntry(m_config_form.ui_cbTransports->name(), m_config_form.ui_cbTransports->currentText());
823 #ifdef CAPTURE_PORTAUDIO
824         m_settings.writeEntry(m_config_form.ui_cbPortAudioDeviceName->name(), m_config_form.ui_cbPortAudioDeviceName->currentText());
825 #endif
826 }
827 void CustomInstrumentTunerForm::loadSettings()
828 {
829         m_settings.load();
830         View::loadAll();
831
832         // views
833         resize(m_settings.readNumEntry("width", 800), m_settings.readNumEntry("height", 600));
834         ui_tbViews->setShown(m_settings.readBoolEntry("ui_tbViews", ui_tbViews->isShown()));
835         ui_tbButtons->setShown(m_settings.readBoolEntry("ui_tbButtons", ui_tbButtons->isShown()));
836
837         // sound capture
838         QString saved_transport = m_settings.readEntry(m_config_form.ui_cbTransports->name(), "");
839         if(saved_transport!="")
840                 for(int i=0; i<m_config_form.ui_cbTransports->count(); i++)
841                         if(m_config_form.ui_cbTransports->text(i)==saved_transport)
842                                 m_config_form.ui_cbTransports->setCurrentItem(i);
843 #ifdef CAPTURE_PORTAUDIO
844 //      cerr << "read: " << m_settings.readEntry("ui_cbPortAudioDeviceName", "default") << endl;
845         QString saved_device = m_settings.readEntry(m_config_form.ui_cbPortAudioDeviceName->name(), "default");
846         try
847         {
848                 PaError err;
849                 err = Pa_Initialize();
850                 if(err != paNoError)
851                         throw QString("PortAudio: CustomInstrumentTunerForm::loadSettings:Pa_Initialize ")+Pa_GetErrorText(err);
852                 int     numDevices = Pa_GetDeviceCount();
853
854                 int current_index = 0;
855                 m_config_form.ui_cbPortAudioDeviceName->clear();
856                 m_config_form.ui_cbPortAudioDeviceName->insertItem("default");
857                 const PaDeviceInfo* deviceInfo;
858                 for(int i=0; i<numDevices; i++)
859                 {
860                         deviceInfo = Pa_GetDeviceInfo(i);
861                         m_config_form.ui_cbPortAudioDeviceName->insertItem(QString(deviceInfo->name));
862                         if(QString(deviceInfo->name)==saved_device)
863                                 current_index = i+1;
864                 }
865                 if(current_index<m_config_form.ui_cbPortAudioDeviceName->count())
866                         m_config_form.ui_cbPortAudioDeviceName->setCurrentItem(current_index);
867         }
868         catch(QString error)
869         {
870                 cerr << "CustomInstrumentTunerForm: ERROR: " << error << endl;
871         }
872         Pa_Terminate();
873 #endif
874 }
875
876 void CustomInstrumentTunerForm::restoreFactorySettings()
877 {
878         if(QMessageBox::question(this, tr("Restore Factory Settings"), tr("This operation is NOT reversible.\nAre you sure you want to lose all your current settings ?"), QMessageBox::Yes, QMessageBox::No)==QMessageBox::Yes)
879         {
880                 m_settings.clear();
881                 View::clearAllSettings();
882
883                 m_settings.removeEntry("width");
884                 m_settings.removeEntry("height");
885                 m_settings.removeEntry("ui_tbViews");
886                 m_settings.removeEntry("ui_tbButtons");
887
888                 m_settings.removeEntry(m_config_form.ui_cbPortAudioDeviceName->name());
889
890                 QMessageBox::information(this, tr("Restore Factory Settings"), tr("You can now restart FMIT to get back factory settings"), QMessageBox::Ok, QMessageBox::NoButton);
891         }
892 }
893
894 void CustomInstrumentTunerForm::helpAbout()
895 {
896         QString text;
897         text = "<h2>Free Music Instrument Tuner</h2>";
898         text += tr("<h3>Version ")+PACKAGE_VERSION;
899         text += tr("</h3><p><h3>Website:</h3><p>homepage: <a href=\"http://home.gna.org/fmit\">http://home.gna.org/fmit</a>");
900         text += tr("<p>development site: <a href=\"http://gna.org/projects/fmit\">http://gna.org/projects/fmit</a>");
901         text += tr("<p>donation link: <a href=\"http://home.gna.org/fmit/donation.html\">http://home.gna.org/fmit/donation.html</a>");
902         text += tr("<p><h3>Author:</h3><p>Gilles Degottex [gilles.degottex@net2000.ch]");
903 #ifdef PACKAGER_STRING
904         if(PACKAGER_STRING!="")
905                 text += tr("<p><h3>Packager:</h3><p>")+QString(PACKAGER_STRING).replace(QChar('<'),"[").replace(QChar('>'),"]");
906 #endif
907
908         QDialog about_dlg(this);
909
910         QTextEdit* textEdit1;
911         QPushButton* pushButton1;
912         QVBoxLayout* Form2Layout;
913         QHBoxLayout* layout1;
914         QSpacerItem* spacer1;
915         QSpacerItem* spacer2;
916
917         about_dlg.setName( tr("about_box") );
918         about_dlg.setCaption( tr("About Free Music Instrument Tuner") );
919         Form2Layout = new QVBoxLayout( &about_dlg, 11, 6, "Form2Layout");
920
921         textEdit1 = new QTextEdit( &about_dlg, "textEdit1" );
922         textEdit1->setText(text);
923         textEdit1->setReadOnly(true);
924         Form2Layout->addWidget( textEdit1 );
925
926         layout1 = new QHBoxLayout( 0, 0, 6, "layout1");
927         spacer1 = new QSpacerItem( 40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum );
928         layout1->addItem( spacer1 );
929
930         pushButton1 = new QPushButton( &about_dlg, "pushButton1" );
931         pushButton1->setText( tr( "OK" ) );
932         layout1->addWidget( pushButton1 );
933         connect(pushButton1, SIGNAL(clicked()), &about_dlg, SLOT(close()));
934         spacer2 = new QSpacerItem( 40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum );
935         layout1->addItem( spacer2 );
936         Form2Layout->addLayout( layout1 );
937         about_dlg.resize( QSize(400, textEdit1->heightForWidth(400)+100) );
938 //      about_dlg.clearWState( WState_Polished );
939
940         about_dlg.exec();
941 }
942
943 CustomInstrumentTunerForm::~CustomInstrumentTunerForm()
944 {
945         if(m_config_form.ui_chkAutoSaveOnExit->isChecked())
946                 saveSettings();
947         else
948         {
949                 m_settings.beginGroup("Auto/");
950                 m_settings.save(m_config_form.ui_chkAutoSaveOnExit);
951                 m_settings.endGroup();
952         }
953 }
954
955 /*
956 bool displayInBrowser(const QString& theURL)
957 {
958 #ifdef _WIN32
959         //TODO replace with less buggy ShellExecuteEx?
960         if ((unsigned
961 int)::ShellExecute(qApp->mainWidget()->winId(),NULL,theURL,NULL,NULL,SW_SHOW)
962 <= 32)
963 {
964                 OFMessageBox::criticalMessageOK(QMessageBox::tr("Unable to display a
965 web browser. Ensure that you have a web browser installed."));
966 }
967 #else
968         //TODO warn if netscape not installed
969         QString aCommand("netscape ");
970         aCommand += theURL;
971         aCommand += " &";
972     if (system(aCommand) != 0)
973 {
974                 OFMessageBox::criticalMessageOK(QMessageBox::tr("Unable to display a
975 netscape browser. You need to have netscape installed and in the
976 path."));
977 }
978
979 return true;
980 }
981 */