1 /***************************************************************************
2 **                                                                        **
3 **  Polyphone, a soundfont editor                                         **
4 **  Copyright (C) 2013-2020 Davy Triponney                                **
5 **                                                                        **
6 **  This program is free software: you can redistribute it and/or modify  **
7 **  it under the terms of the GNU General Public License as published by  **
8 **  the Free Software Foundation, either version 3 of the License, or     **
9 **  (at your option) any later version.                                   **
10 **                                                                        **
11 **  This program is distributed in the hope that it will be useful,       **
12 **  but WITHOUT ANY WARRANTY; without even the implied warranty of        **
13 **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the          **
14 **  GNU General Public License for more details.                          **
15 **                                                                        **
16 **  You should have received a copy of the GNU General Public License     **
17 **  along with this program. If not, see http://www.gnu.org/licenses/.    **
18 **                                                                        **
19 ****************************************************************************
20 **           Author: Davy Triponney                                       **
21 **  Website/Contact: https://www.polyphone-soundfonts.com                 **
22 **             Date: 01.01.2013                                           **
23 ***************************************************************************/
24 
25 #include "toolmixturecreation_gui.h"
26 #include "ui_toolmixturecreation_gui.h"
27 #include "contextmanager.h"
28 #include <QMessageBox>
29 
ToolMixtureCreation_gui(QWidget * parent)30 ToolMixtureCreation_gui::ToolMixtureCreation_gui(QWidget *parent) :
31     AbstractToolGui(parent),
32     ui(new Ui::ToolMixtureCreation_gui)
33 {
34     ui->setupUi(this);
35 
36     // Icons
37     ui->pushAddDiv->setIcon(ContextManager::theme()->getColoredSvg(":/icons/document-new.svg", QSize(16, 16), ThemeManager::BUTTON_TEXT));
38     ui->pushRemoveDiv->setIcon(ContextManager::theme()->getColoredSvg(":/icons/minus.svg", QSize(16, 16), ThemeManager::BUTTON_TEXT));
39     ui->pushAddRank->setIcon(ContextManager::theme()->getColoredSvg(":/icons/document-new.svg", QSize(16, 16), ThemeManager::BUTTON_TEXT));
40     ui->pushRemoveRank->setIcon(ContextManager::theme()->getColoredSvg(":/icons/minus.svg", QSize(16, 16), ThemeManager::BUTTON_TEXT));
41 }
42 
~ToolMixtureCreation_gui()43 ToolMixtureCreation_gui::~ToolMixtureCreation_gui()
44 {
45     delete ui;
46 }
47 
updateInterface(AbstractToolParameters * parameters,IdList ids)48 void ToolMixtureCreation_gui::updateInterface(AbstractToolParameters * parameters, IdList ids)
49 {
50     Q_UNUSED(ids)
51     ToolMixtureCreation_parameters * params = dynamic_cast<ToolMixtureCreation_parameters *>(parameters);
52 
53     // Recall values
54     _divisions = params->getDivisions();
55     ui->lineNom->setText(params->getInstrumentName());
56     ui->comboFreq->setCurrentIndex(params->getDensityType());
57     ui->checkBouclage->setChecked(params->getLoopSample());
58     ui->checkStereo->setChecked(params->getStereoSample());
59 
60     // Select octaves
61     this->on_comboType1_currentIndexChanged(0);
62 
63     // Display divisions
64     this->dispDiv();
65 }
66 
saveParameters(AbstractToolParameters * parameters)67 void ToolMixtureCreation_gui::saveParameters(AbstractToolParameters * parameters)
68 {
69     // Save values
70     ToolMixtureCreation_parameters * params = dynamic_cast<ToolMixtureCreation_parameters *>(parameters);
71     params->setDivisions(_divisions);
72     params->setInstrumentName(ui->lineNom->text());
73     params->setDensityType(ui->comboFreq->currentIndex());
74     params->setLoopSample(ui->checkBouclage->isChecked());
75     params->setStereoSample(ui->checkStereo->isChecked());
76 }
77 
78 // Display
79 
dispDiv()80 void ToolMixtureCreation_gui::dispDiv()
81 {
82     // Clear the list
83     ui->listDivisions->blockSignals(true);
84     ui->listDivisions->clear();
85     ui->listRangs->clear();
86 
87     // Fill the list
88     int valMin, valMax;
89     foreach (DivisionInfo di, _divisions)
90     {
91         // Add an element
92         valMin = qMin(di.getMinKey(), di.getMaxKey());
93         valMax = qMax(di.getMinKey(), di.getMaxKey());
94         ui->listDivisions->addItem(ContextManager::keyName()->getKeyName(valMin) + " - " +
95                                    ContextManager::keyName()->getKeyName(valMax));
96     }
97     ui->listDivisions->blockSignals(false);
98 }
99 
dispRang(int numDiv)100 void ToolMixtureCreation_gui::dispRang(int numDiv)
101 {
102     // Effacement liste rangs
103     ui->listRangs->blockSignals(true);
104     ui->listRangs->clear();
105     if (ui->listDivisions->selectedItems().length() == 1)
106     {
107         // Numéro de la division
108         if (numDiv == -1)
109             numDiv = this->getNumDiv();
110         if (numDiv < 0) return;
111         DivisionInfo di = _divisions.at(numDiv);
112         foreach (RankInfo ri, di.getRanks())
113             ui->listRangs->addItem(getText(ri.getOvertoneType(), ri.getOctave()));
114     }
115     ui->listRangs->blockSignals(false);
116 }
117 
getText(int overtoneType,int octave)118 QString ToolMixtureCreation_gui::getText(int overtoneType, int octave)
119 {
120     QString text;
121     switch (overtoneType)
122     {
123     case 0: // octaves
124         switch (octave)
125         {
126         case  0: text = "64'"  ; break;
127         case  1: text = "32'"  ; break;
128         case  2: text = "16'"  ; break;
129         case  3: text = "8'"   ; break;
130         case  4: text = "4'"   ; break;
131         case  5: text = "2'"   ; break;
132         case  6: text = "1'"   ; break;
133         case  7: text = "1/2'" ; break;
134         case  8: text = "1/4'" ; break;
135         case  9: text = "1/8'" ; break;
136         case 10: text = "1/16'"; break;
137         }
138         break;
139     case 1: // quintes
140         switch (octave)
141         {
142         case 0: text = "21' 1/3"; break;
143         case 1: text = "10' 2/3"; break;
144         case 2: text = "5' 1/3" ; break;
145         case 3: text = "2' 2/3" ; break;
146         case 4: text = "1' 1/3" ; break;
147         case 5: text = "2/3'"   ; break;
148         case 6: text = "1/3'"   ; break;
149         case 7: text = "1/6'"   ; break;
150         case 8: text = "1/12'"  ; break;
151         }
152         break;
153     case 2: // tierces
154         switch (octave)
155         {
156         case 0: text = "12' 4/5"; break;
157         case 1: text = "6' 2/5" ; break;
158         case 2: text = "3' 1/5" ; break;
159         case 3: text = "1' 3/5" ; break;
160         case 4: text = "4/5'"   ; break;
161         case 5: text = "2/5'"   ; break;
162         case 6: text = "1/5'"   ; break;
163         case 7: text = "1/10'"  ; break;
164         }
165         break;
166     case 3: // septièmes
167         switch (octave)
168         {
169         case 0: text = "9' 1/7"; break;
170         case 1: text = "4' 4/7"; break;
171         case 2: text = "2' 2/7"; break;
172         case 3: text = "1' 1/7"; break;
173         case 4: text = "4/7'"  ; break;
174         case 5: text = "2/7'"  ; break;
175         case 6: text = "1/7'"  ; break;
176         case 7: text = "1/14'" ; break;
177         }
178         break;
179     case 4: // neuvièmes
180         switch (octave)
181         {
182         case 0: text = "7' 1/9"; break;
183         case 1: text = "3' 5/9"; break;
184         case 2: text = "1' 7/9"; break;
185         case 3: text = "8/9'"  ; break;
186         case 4: text = "4/9'"  ; break;
187         case 5: text = "2/9'"  ; break;
188         case 6: text = "1/9'"  ; break;
189         }
190         break;
191     case 5: // onzièmes
192         switch (octave)
193         {
194         case 0: text = "5' 9/11" ; break;
195         case 1: text = "2' 10/11"; break;
196         case 2: text = "1' 5/11" ; break;
197         case 3: text = "8/11'"   ; break;
198         case 4: text = "4/11'"   ; break;
199         case 5: text = "2/11'"   ; break;
200         case 6: text = "1/11'"   ; break;
201         }
202         break;
203     case 6: // treizièmes
204         switch (octave)
205         {
206         case 0: text = "4' 12/13"; break;
207         case 1: text = "2' 6/13"; break;
208         case 2: text = "1' 3/13"; break;
209         case 3: text = "8/13'"  ; break;
210         case 4: text = "4/13'"  ; break;
211         case 5: text = "2/13'"  ; break;
212         case 6: text = "1/13'"  ; break;
213         }
214         break;
215     case 7: // quinzièmes
216         switch (octave)
217         {
218         case 0: text = "4' 4/15"; break;
219         case 1: text = "2' 2/15"; break;
220         case 2: text = "1' 1/15"; break;
221         case 3: text = "8/15'"  ; break;
222         case 4: text = "4/15'"  ; break;
223         case 5: text = "2/15'"  ; break;
224         case 6: text = "1/15'"  ; break;
225         }
226         break;
227     case 8: // dix-septièmes
228         switch (octave)
229         {
230         case 0: text = "3' 13/17"; break;
231         case 1: text = "1' 15/17"; break;
232         case 2: text = "16/17'"  ; break;
233         case 3: text = "8/17'"   ; break;
234         case 4: text = "4/17'"   ; break;
235         case 5: text = "2/17'"   ; break;
236         }
237         break;
238     case 9: // dix-neuvièmes
239         switch (octave)
240         {
241         case 0: text = "3' 7/19" ; break;
242         case 1: text = "1' 13/19"; break;
243         case 2: text = "16/19'"  ; break;
244         case 3: text = "8/19'"   ; break;
245         case 4: text = "4/19'"   ; break;
246         case 5: text = "2/19'"   ; break;
247         }
248         break;
249     case 10: // vingt-et-unièmes
250         switch (octave)
251         {
252         case 0: text = "3' 1/21" ; break;
253         case 1: text = "1' 11/21"; break;
254         case 2: text = "16/21'"  ; break;
255         case 3: text = "8/21'"   ; break;
256         case 4: text = "4/21'"   ; break;
257         case 5: text = "2/21'"   ; break;
258         }
259         break;
260     case 11: // vingt-troisièmes
261         switch (octave)
262         {
263         case 0: text = "2' 18/23"; break;
264         case 1: text = "1' 9/23" ; break;
265         case 2: text = "16/23'"  ; break;
266         case 3: text = "8/23'"   ; break;
267         case 4: text = "4/23'"   ; break;
268         case 5: text = "2/23'"   ; break;
269         }
270         break;
271     case 12: // vingt-cinquièmes
272         switch (octave)
273         {
274         case 0: text = "2' 14/25"; break;
275         case 1: text = "1' 7/25" ; break;
276         case 2: text = "16/25'"  ; break;
277         case 3: text = "8/25'"   ; break;
278         case 4: text = "4/25'"   ; break;
279         case 5: text = "2/25'"   ; break;
280         }
281         break;
282     case 13: // vingt-septièmes
283         switch (octave)
284         {
285         case 0: text = "2' 10/27"; break;
286         case 1: text = "1' 5/27" ; break;
287         case 2: text = "16/27'"  ; break;
288         case 3: text = "8/27'"   ; break;
289         case 4: text = "4/27'"   ; break;
290         case 5: text = "2/27'"   ; break;
291         }
292         break;
293     }
294     return text;
295 }
296 
dispType2(int index)297 void ToolMixtureCreation_gui::dispType2(int index)
298 {
299     // Reset du combobox 2
300     ui->comboType2->blockSignals(true);
301     ui->comboType2->clear();
302 
303     // Nombre d'éléments en fonction du type de mutation
304     int nbItem = 0;
305     if (index == 0)
306         nbItem = 11;
307     else if (index == 1)
308         nbItem = 9;
309     else if (index  < 4)
310         nbItem = 8;
311     else if (index < 8)
312         nbItem = 7;
313     else
314         nbItem = 6;
315 
316     for (int i = 0; i < nbItem; i++)
317         ui->comboType2->addItem(getText(index, i));
318 
319     ui->comboType2->blockSignals(false);
320 }
321 
getNumDiv()322 int ToolMixtureCreation_gui::getNumDiv()
323 {
324     if (ui->listDivisions->selectedItems().length() != 1)
325         return -1;
326     int numDiv =  ui->listDivisions->row(ui->listDivisions->selectedItems().first());
327     if (_divisions.length() <= numDiv)
328         return -1;
329     return numDiv;
330 }
331 
getNumRang()332 int ToolMixtureCreation_gui::getNumRang()
333 {
334     if (ui->listRangs->selectedItems().length() != 1)
335         return -1;
336     int numRang = ui->listRangs->row(ui->listRangs->selectedItems().first());
337     int numDiv = this->getNumDiv();
338     if (numDiv < 0)
339         return -1;
340     if (_divisions[numDiv].getRanks().count() <= numRang)
341         return -1;
342     return numRang;
343 }
344 
345 // Edit divisions
346 
on_listDivisions_itemSelectionChanged()347 void ToolMixtureCreation_gui::on_listDivisions_itemSelectionChanged()
348 {
349     int currentRow = ui->listDivisions->currentRow();
350 
351     // Modification des spinboxes
352     DivisionInfo di = _divisions[currentRow];
353     ui->spinDivStart->blockSignals(true);
354     ui->spinDivEnd->blockSignals(true);
355     ui->spinDivStart->setValue(di.getMinKey());
356     ui->spinDivEnd->setValue(di.getMaxKey());
357     ui->spinDivStart->blockSignals(false);
358     ui->spinDivEnd->blockSignals(false);
359 
360     // Affichage rangs
361     this->dispRang(currentRow);
362 }
363 
on_pushAddDiv_pressed()364 void ToolMixtureCreation_gui::on_pushAddDiv_pressed()
365 {
366     // Ajdd a division
367     _divisions << DivisionInfo(ui->spinDivStart->value(), ui->spinDivEnd->value());
368 
369     // Recopie des rangs précédents
370     int numDiv = this->getNumDiv();
371     if (numDiv >= 0)
372         foreach (RankInfo ri, _divisions[numDiv].getRanks())
373             _divisions[_divisions.size() - 1].addRank(ri.getOvertoneType(), ri.getOctave());
374 
375     // Affichage
376     this->dispDiv();
377 
378     // Sélection de l'élément nouvellement créé
379     ui->listDivisions->setCurrentRow(_divisions.length()-1);
380 }
381 
on_pushRemoveDiv_pressed()382 void ToolMixtureCreation_gui::on_pushRemoveDiv_pressed()
383 {
384     int numDiv = this->getNumDiv();
385     if (numDiv < 0) return;
386     _divisions.removeAt(numDiv);
387 
388     // Affichage
389     this->dispDiv();
390 
391     // Selection
392     if (numDiv >= ui->listDivisions->count())
393         numDiv--;
394     if (numDiv >= 0)
395         ui->listDivisions->setCurrentRow(numDiv);
396     else
397         this->dispRang();
398 }
399 
on_spinDivStart_valueChanged(int arg1)400 void ToolMixtureCreation_gui::on_spinDivStart_valueChanged(int arg1)
401 {
402     int numDiv = this->getNumDiv();
403     if (numDiv < 0)
404         return;
405     _divisions[numDiv].setMinKey(arg1);
406 
407     // Affichage
408     this->dispDiv();
409 
410     // Resélection de l'élément
411     ui->listDivisions->blockSignals(true);
412     ui->listDivisions->setCurrentRow(numDiv);
413     ui->listDivisions->blockSignals(false);
414 }
415 
on_spinDivEnd_valueChanged(int arg1)416 void ToolMixtureCreation_gui::on_spinDivEnd_valueChanged(int arg1)
417 {
418     int numDiv = this->getNumDiv();
419     if (numDiv < 0) return;
420     _divisions[numDiv].setMaxKey(arg1);
421 
422     // Affichage
423     this->dispDiv();
424 
425     // Resélection de l'élément
426     ui->listDivisions->blockSignals(true);
427     ui->listDivisions->setCurrentRow(numDiv);
428     ui->listDivisions->blockSignals(false);
429 }
430 
431 // Edit ranks
432 
on_listRangs_itemSelectionChanged()433 void ToolMixtureCreation_gui::on_listRangs_itemSelectionChanged()
434 {
435     int currentRow = ui->listRangs->currentRow();
436 
437     // Récupération numéro de division et rang
438     int numDiv = this->getNumDiv();
439     if (numDiv < 0) return;
440     if (currentRow >= _divisions[numDiv].getRanks().count())
441         return;
442 
443     // Update comboboxes
444     ui->comboType1->blockSignals(true);
445     ui->comboType2->blockSignals(true);
446     ui->comboType1->setCurrentIndex(_divisions[numDiv].getRanks()[currentRow].getOvertoneType());
447     this->dispType2(_divisions[numDiv].getRanks()[currentRow].getOvertoneType());
448     ui->comboType2->blockSignals(true);
449     ui->comboType2->setCurrentIndex(_divisions[numDiv].getRanks()[currentRow].getOctave());
450     ui->comboType1->blockSignals(false);
451     ui->comboType2->blockSignals(false);
452 }
453 
on_pushAddRank_clicked()454 void ToolMixtureCreation_gui::on_pushAddRank_clicked()
455 {
456     // Récupération numéro de division
457     int numDiv = this->getNumDiv();
458     if (numDiv < 0) return;
459 
460     // Ajout d'un rang
461     int type1 = ui->comboType1->currentIndex();
462     int type2 = ui->comboType2->currentIndex();
463     _divisions[numDiv].addRank(type1, type2);
464 
465     // Affichage
466     this->dispRang();
467 
468     // Sélection de l'élément nouvellement créé
469     ui->listRangs->setCurrentRow(_divisions[numDiv].getRanks().count() - 1);
470 }
471 
on_pushRemoveRank_clicked()472 void ToolMixtureCreation_gui::on_pushRemoveRank_clicked()
473 {
474     // Récupération numéro de division et rang
475     int numDiv = this->getNumDiv();
476     if (numDiv < 0) return;
477     int numRang = this->getNumRang();
478     if (numRang < 0) return;
479 
480     // Remove a rank
481     _divisions[numDiv].removeRank(numRang);
482 
483     // Affichage
484     this->dispRang();
485 
486     // Selection
487     if (numRang >= ui->listRangs->count())
488         numRang--;
489     if (numRang >= 0)
490         ui->listRangs->setCurrentRow(numRang);
491 }
492 
on_comboType1_currentIndexChanged(int index)493 void ToolMixtureCreation_gui::on_comboType1_currentIndexChanged(int index)
494 {
495     // Modification combobox type2
496     this->dispType2(index);
497 
498     // Sélection du 4ème élément (fondamentale 8')
499     ui->comboType2->setCurrentIndex(3);
500 }
501 
on_comboType2_currentIndexChanged(int index)502 void ToolMixtureCreation_gui::on_comboType2_currentIndexChanged(int index)
503 {
504     // Récupération numéro de division
505     int numDiv = this->getNumDiv();
506     if (numDiv < 0) return;
507 
508     // Récupération numéro de rang
509     int numRang = this->getNumRang();
510     if (numRang < 0) return;
511 
512     // Modification liste paramètres
513     _divisions[numDiv].getRanks()[numRang].setOvertoneType(ui->comboType1->currentIndex());
514     _divisions[numDiv].getRanks()[numRang].setOctave(index);
515 
516     // Affichage
517     this->dispRang();
518 
519     // Resélection de l'élément
520     ui->listRangs->blockSignals(true);
521     ui->listRangs->setCurrentRow(numRang);
522     ui->listRangs->blockSignals(false);
523 }
524 
on_pushCancel_clicked()525 void ToolMixtureCreation_gui::on_pushCancel_clicked()
526 {
527     emit(this->canceled());
528 }
529 
on_pushOk_clicked()530 void ToolMixtureCreation_gui::on_pushOk_clicked()
531 {
532     // Check that the name is specified
533     if (ui->lineNom->text().isEmpty())
534     {
535         QMessageBox::warning(this, tr("Warning"), tr("The instrument name must be provided."));
536         return;
537     }
538 
539     // Check that at least one division is not empty
540     bool ok = false;
541     for (int i = 0; i < _divisions.length(); i++)
542     {
543         if (!_divisions[i].getRanks().empty())
544         {
545             ok = true;
546             break;
547         }
548     }
549     if (!ok)
550     {
551         QMessageBox::warning(this, tr("Warning"), tr("At least one rank must be specified."));
552         return;
553     }
554 
555     emit(this->validated());
556 }
557