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