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