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 "pageprst.h"
26 #include "ui_pageprst.h"
27 #include "contextmanager.h"
28 #include <QMenu>
29 #include "modulatorsplitter.h"
30 
31 // Constructeur, destructeur
PagePrst(QWidget * parent)32 PagePrst::PagePrst(QWidget *parent) :
33     PageTable(PAGE_PRST, parent),
34     ui(new Ui::PagePrst)
35 {
36     ui->setupUi(this);
37 
38     // Style
39     ui->frameBottom->setStyleSheet("QFrame{background-color:" +
40                                    ContextManager::theme()->getColor(ThemeManager::HIGHLIGHTED_BACKGROUND).name() + ";color:" +
41                                    ContextManager::theme()->getColor(ThemeManager::HIGHLIGHTED_TEXT).name() + "}");
42     ui->tablePrst->setStyleSheet("QTableWidget{border:1px solid " +
43                                  this->palette().dark().color().name() +
44                                  ";border-top:0;border-left:0;border-right:0}");
45 
46     this->contenant = elementPrst;
47     this->contenantGen = elementPrstGen;
48     this->contenantMod = elementPrstMod;
49     this->contenu = elementInst;
50     this->lien = elementPrstInst;
51     this->lienGen = elementPrstInstGen;
52     this->lienMod = elementPrstInstMod;
53     this->_table = ui->tablePrst;
54     _rangeEditor = ui->rangeEditor;
55     _envelopEditor = nullptr;
56     _modulatorEditor = ui->modulatorEditor;
57 
58     // Initialization of spinBoxes
59     ui->spinBank->init(this);
60     ui->spinPreset->init(this);
61 
62 #ifdef Q_OS_MAC
63     _table->setStyleSheet("QHeaderView::section:horizontal{padding: 4px 10px 4px 10px;}");
64     QFont fontTmp = _table->font();
65     fontTmp.setPixelSize(10);
66     _table->setFont(fontTmp);
67 #endif
68     ui->tablePrst->verticalHeader()->setDefaultSectionSize(QFontMetrics(ui->tablePrst->font()).height() + 8);
69 
70     QFont font = ui->tablePrst->font();
71     font.setPixelSize(11);
72     ui->tablePrst->horizontalHeader()->setFont(font);
73     ui->tablePrst->verticalHeader()->setFont(font);
74 
75     connect(this->_table, SIGNAL(actionBegin()), this, SLOT(actionBegin()));
76     connect(this->_table, SIGNAL(actionFinished()), this, SLOT(actionFinished()));
77     connect(this->_table, SIGNAL(openElement(EltID)), this, SLOT(onOpenElement(EltID)));
78     connect(ui->rangeEditor, SIGNAL(updateKeyboard()), this, SLOT(customizeKeyboard()));
79     connect(ui->rangeEditor, SIGNAL(divisionsSelected(IdList)), this, SIGNAL(selectedIdsChanged(IdList)));
80     connect(ui->modulatorEditor, SIGNAL(attributesSelected(QList<AttributeType>)), this, SLOT(onModSelectionChanged(QList<AttributeType>)));
81 
82     // QSplitter for being able to resize the modulator area
83     ModulatorSplitter * splitter = new ModulatorSplitter(ui->page, ui->tablePrst, ui->modulatorEditor, true);
84     QVBoxLayout * layout = dynamic_cast<QVBoxLayout *>(ui->page->layout());
85     layout->addWidget(splitter);
86 }
87 
~PagePrst()88 PagePrst::~PagePrst()
89 {
90     delete ui;
91 }
92 
getDisplayOptions(IdList selectedIds)93 QList<Page::DisplayOption> PagePrst::getDisplayOptions(IdList selectedIds)
94 {
95     return QList<DisplayOption>()
96             << DisplayOption(1, ":/icons/table.svg", tr("Table"))
97             << DisplayOption(2, ":/icons/range.svg", tr("Ranges"), selectedIds.isElementUnique(elementPrst));
98 }
99 
updateInterface(QString editingSource,IdList selectedIds,int displayOption)100 bool PagePrst::updateInterface(QString editingSource, IdList selectedIds, int displayOption)
101 {
102     if (selectedIds.empty())
103         return false;
104 
105     // Check if the new parents are the same
106     IdList parentIds = selectedIds.getSelectedIds(elementPrst);
107     bool sameElement = true;
108     if (parentIds.count() == _currentParentIds.count())
109     {
110         for (int i = 0; i < parentIds.count(); i++)
111         {
112             if (parentIds[i] != _currentParentIds[i])
113             {
114                 sameElement = false;
115                 break;
116             }
117         }
118     }
119     else
120         sameElement = false;
121     bool justSelection = (sameElement && editingSource == "command:selection");
122 
123     _currentParentIds = parentIds;
124     _currentIds = selectedIds;
125 
126     if (_currentParentIds.count() == 1)
127     {
128         ui->spinBank->setEnabled(true);
129         ui->spinPreset->setEnabled(true);
130         ui->modulatorEditor->show();
131         EltID id = _currentParentIds.first();
132         id.typeElement = elementPrst;
133         ui->spinBank->setValue(_sf2->get(id, champ_wBank).wValue);
134         ui->spinPreset->setValue(_sf2->get(id, champ_wPreset).wValue);
135         ui->labelPercussion->setVisible(_sf2->get(id, champ_wBank).wValue == 128);
136     }
137     else
138     {
139         // Check if the bank is the same in the selection
140         bool sameNumber = true;
141         int tmp = -1;
142         foreach (EltID id, _currentParentIds)
143         {
144             int number = _sf2->get(id, champ_wBank).wValue;
145             if (tmp == -1)
146                 tmp = number;
147             else if (tmp != number)
148             {
149                 sameNumber = false;
150                 break;
151             }
152         }
153         ui->spinBank->setEnabled(sameNumber);
154         if (sameNumber)
155             ui->spinBank->setValue(tmp);
156 
157         // Check if the preset is the same in the selection
158         sameNumber = true;
159         tmp = -1;
160         foreach (EltID id, _currentParentIds)
161         {
162             int number = _sf2->get(id, champ_wPreset).wValue;
163             if (tmp == -1)
164                 tmp = number;
165             else if (tmp != number)
166             {
167                 sameNumber = false;
168                 break;
169             }
170         }
171         ui->spinPreset->setEnabled(sameNumber);
172         if (sameNumber)
173             ui->spinPreset->setValue(tmp);
174 
175         // Hide modulators
176         ui->modulatorEditor->hide();
177 
178         // Hide the percussion label
179         ui->labelPercussion->hide();
180     }
181 
182     switch (displayOption)
183     {
184     case 1:
185         ui->stackedWidget->setCurrentIndex(0);
186         this->afficheTable(justSelection);
187         break;
188     case 2:
189         ui->stackedWidget->setCurrentIndex(1);
190         this->afficheRanges(justSelection);
191         break;
192     default:
193         return false;
194     }
195     customizeKeyboard();
196 
197     return true;
198 }
199 
200 // TableWidgetPrst
TableWidgetPrst(QWidget * parent)201 TableWidgetPrst::TableWidgetPrst(QWidget *parent) : TableWidget(parent)
202 {
203     _fieldList << champ_keyRange
204                << champ_velRange
205                << champ_initialAttenuation
206                << champ_pan
207                << champ_coarseTune
208                << champ_fineTune
209                << champ_scaleTuning
210                << champ_initialFilterFc
211                << champ_initialFilterQ
212                << champ_delayVolEnv
213                << champ_attackVolEnv
214                << champ_holdVolEnv
215                << champ_decayVolEnv
216                << champ_sustainVolEnv
217                << champ_releaseVolEnv
218                << champ_keynumToVolEnvHold
219                << champ_keynumToVolEnvDecay
220                << champ_delayModEnv
221                << champ_attackModEnv
222                << champ_holdModEnv
223                << champ_decayModEnv
224                << champ_sustainModEnv
225                << champ_releaseModEnv
226                << champ_modEnvToPitch
227                << champ_modEnvToFilterFc
228                << champ_keynumToModEnvHold
229                << champ_keynumToModEnvDecay
230                << champ_delayModLFO
231                << champ_freqModLFO
232                << champ_modLfoToPitch
233                << champ_modLfoToFilterFc
234                << champ_modLfoToVolume
235                << champ_delayVibLFO
236                << champ_freqVibLFO
237                << champ_vibLfoToPitch
238                << champ_chorusEffectsSend
239                << champ_reverbEffectsSend;
240 
241     this->setRowCount(_fieldList.count() + 1);
242     for (int i = 1; i < this->rowCount(); i++)
243         this->setVerticalHeaderItem(i, new QTableWidgetItem(Attribute::getDescription(_fieldList[i - 1], true)));
244 
245     // Unit warning
246     this->verticalHeaderItem(3)->setToolTip(tr("Values on this row are expressed in real dB.\nOther soundfont editors might display other units."));
247     this->verticalHeaderItem(3)->setData(Qt::DecorationRole,
248                                          ContextManager::theme()->getColoredSvg(":/icons/info.svg", QSize(12, 12),
249                                                                                 ThemeManager::HIGHLIGHTED_BACKGROUND));
250 }
251 
~TableWidgetPrst()252 TableWidgetPrst::~TableWidgetPrst() {}
253 
getRow(AttributeType champ)254 int TableWidgetPrst::getRow(AttributeType champ)
255 {
256     return _fieldList.indexOf(champ) + 1;
257 }
258 
getChamp(int row)259 AttributeType TableWidgetPrst::getChamp(int row)
260 {
261     row--;
262     if (row >= 0 && row < _fieldList.count())
263         return _fieldList[row];
264     return champ_unknown;
265 }
266 
spinUpDown(int steps,SpinBox * spin)267 void PagePrst::spinUpDown(int steps, SpinBox *spin)
268 {
269     if (_preparingPage || _currentParentIds.empty() || steps == 0)
270         return;
271 
272     _preparingPage = true;
273     if (spin == ui->spinBank)
274         this->setBank(ui->spinBank->value(), steps > 0 ? 1 : -1);
275     else
276         this->setPreset(ui->spinPreset->value(), steps > 0 ? 1 : -1);
277     _preparingPage = false;
278 }
279 
setBank()280 void PagePrst::setBank()
281 {
282     if (_preparingPage)
283         return;
284     _preparingPage = true;
285     this->setBank(ui->spinBank->value(), 0);
286     _preparingPage = false;
287 }
288 
setPreset()289 void PagePrst::setPreset()
290 {
291     if (_preparingPage)
292         return;
293     _preparingPage = true;
294     this->setPreset(ui->spinPreset->value(), 0);
295     _preparingPage = false;
296 }
297 
keyPlayedInternal2(int key,int velocity)298 void PagePrst::keyPlayedInternal2(int key, int velocity)
299 {
300     IdList ids = _currentIds.getSelectedIds(elementPrst);
301     if (ids.count() == 1)
302         ContextManager::audio()->getSynth()->play(ids[0], key, velocity);
303 }
304 
setBank(quint16 desiredBank,int collisionResolution)305 void PagePrst::setBank(quint16 desiredBank, int collisionResolution)
306 {
307     // Previous bank
308     if (_currentParentIds.empty())
309         return;
310     quint16 previousBank = _sf2->get(_currentParentIds[0], champ_wBank).wValue;
311 
312     // Find the bank to set, according to the collision resolution method
313     while (!isBankAvailable(desiredBank))
314     {
315         bool goBack = false;
316         if (collisionResolution == 0)
317             goBack = true;
318         else if (collisionResolution > 0)
319         {
320             if (desiredBank == 128) // Max is 128
321                 goBack = true;
322             else
323                 desiredBank++;
324         }
325         else if (collisionResolution < 0)
326         {
327             if (desiredBank == 0)
328                 goBack = true;
329             else
330                 desiredBank--;
331         }
332 
333         if (goBack)
334         {
335             // Back to the previous bank
336             ui->spinBank->setValue(previousBank);
337             return;
338         }
339     }
340 
341     // A bank has been found, the current ids are edited
342     AttributeValue v;
343     v.wValue = desiredBank;
344     foreach (EltID id, _currentParentIds)
345         _sf2->set(id, champ_wBank, v);
346     _sf2->endEditing(getEditingSource());
347 
348     // GUI update
349     ui->spinBank->setValue(desiredBank);
350     ui->labelPercussion->setVisible(ui->spinBank->value() == 128);
351 }
352 
isBankAvailable(quint16 wBank)353 bool PagePrst::isBankAvailable(quint16 wBank)
354 {
355     // Current sf2
356     if (_currentParentIds.empty())
357         return false;
358     int indexSf2 = _currentParentIds[0].indexSf2;
359 
360     // Is it possible to change the bank of all current ids?
361     QList<int> usePresets = getUsedPresetsForBank(indexSf2, wBank);
362     foreach (EltID id, _currentParentIds)
363         if (usePresets.contains(_sf2->get(id, champ_wPreset).wValue))
364             return false;
365 
366     return true;
367 }
368 
getUsedPresetsForBank(int sf2Index,quint16 wBank)369 QList<int> PagePrst::getUsedPresetsForBank(int sf2Index, quint16 wBank)
370 {
371     QList<int> ret;
372     EltID id(elementPrst, sf2Index);
373     foreach (int i, _sf2->getSiblings(id))
374     {
375         id.indexElt = i;
376         if (_sf2->get(id, champ_wBank).wValue == wBank)
377             ret << _sf2->get(id, champ_wPreset).wValue;
378     }
379     return ret;
380 }
381 
setPreset(quint16 desiredPreset,int collisionResolution)382 void PagePrst::setPreset(quint16 desiredPreset, int collisionResolution)
383 {
384     // Previous preset
385     if (_currentParentIds.empty())
386         return;
387     quint16 previousPreset = _sf2->get(_currentParentIds[0], champ_wPreset).wValue;
388 
389     // Find the preset to set, according to the collision resolution method
390     while (!isPresetAvailable(desiredPreset))
391     {
392         bool goBack = false;
393         if (collisionResolution == 0)
394             goBack = true;
395         else if (collisionResolution > 0)
396         {
397             if (desiredPreset == 127) // Max is 127
398                 goBack = true;
399             else
400                 desiredPreset++;
401         }
402         else if (collisionResolution < 0)
403         {
404             if (desiredPreset == 0)
405                 goBack = true;
406             else
407                 desiredPreset--;
408         }
409 
410         if (goBack)
411         {
412             // Back to the previous preset
413             ui->spinPreset->setValue(previousPreset);
414             return;
415         }
416     }
417 
418     // A preset has been found, the current ids are edited
419     AttributeValue v;
420     v.wValue = desiredPreset;
421     foreach (EltID id, _currentParentIds)
422         _sf2->set(id, champ_wPreset, v);
423     _sf2->endEditing(getEditingSource());
424 
425     // GUI update
426     ui->spinPreset->setValue(desiredPreset);
427 }
428 
isPresetAvailable(quint16 wPreset)429 bool PagePrst::isPresetAvailable(quint16 wPreset)
430 {
431     // Current sf2
432     if (_currentParentIds.empty())
433         return false;
434     int indexSf2 = _currentParentIds[0].indexSf2;
435 
436     // Is it possible to change the preset of all current ids?
437     QList<int> useBanks = getUsedBanksForPreset(indexSf2, wPreset);
438     foreach (EltID id, _currentParentIds)
439         if (useBanks.contains(_sf2->get(id, champ_wBank).wValue))
440             return false;
441 
442     return true;
443 }
444 
getUsedBanksForPreset(int sf2Index,quint16 wPreset)445 QList<int> PagePrst::getUsedBanksForPreset(int sf2Index, quint16 wPreset)
446 {
447     QList<int> ret;
448     EltID id(elementPrst, sf2Index);
449     foreach (int i, _sf2->getSiblings(id))
450     {
451         id.indexElt = i;
452         if (_sf2->get(id, champ_wPreset).wValue == wPreset)
453             ret << _sf2->get(id, champ_wBank).wValue;
454     }
455     return ret;
456 }
457