1 // qsamplerInstrumentForm.cpp
2 //
3 /****************************************************************************
4    Copyright (C) 2003-2021, rncbc aka Rui Nuno Capela. All rights reserved.
5    Copyright (C) 2007, Christian Schoenebeck
6 
7    This program is free software; you can redistribute it and/or
8    modify it under the terms of the GNU General Public License
9    as published by the Free Software Foundation; either version 2
10    of the License, or (at your option) any later version.
11 
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16 
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 
21 *****************************************************************************/
22 
23 #include "qsamplerAbout.h"
24 #include "qsamplerInstrumentForm.h"
25 
26 #include "qsamplerOptions.h"
27 #include "qsamplerChannel.h"
28 #include "qsamplerMainForm.h"
29 
30 #include <QMessageBox>
31 #include <QPushButton>
32 #include <QFileDialog>
33 
34 // Needed for lroundf()
35 #ifdef CONFIG_ROUND
36 #include <cmath>
37 #else
lroundf(float x)38 static inline long lroundf ( float x )
39 {
40 	if (x >= 0.0f)
41 		return long(x + 0.5f);
42 	else
43 		return long(x - 0.5f);
44 }
45 #endif
46 
47 
48 namespace QSampler {
49 
50 //-------------------------------------------------------------------------
51 // QSampler::InstrumentForm -- Instrument map item form implementation.
52 //
53 
InstrumentForm(QWidget * pParent)54 InstrumentForm::InstrumentForm ( QWidget *pParent )
55 	: QDialog(pParent)
56 {
57 	m_ui.setupUi(this);
58 
59 	// Initialize locals.
60 	m_pInstrument = nullptr;
61 
62 	m_iDirtySetup = 0;
63 	m_iDirtyCount = 0;
64 	m_iDirtyName  = 0;
65 
66 	// Try to restore normal window positioning.
67 	adjustSize();
68 
69 
70 	QObject::connect(m_ui.MapComboBox,
71 		SIGNAL(activated(int)),
72 		SLOT(changed()));
73 	QObject::connect(m_ui.BankSpinBox,
74 		SIGNAL(valueChanged(int)),
75 		SLOT(changed()));
76 	QObject::connect(m_ui.ProgSpinBox,
77 		SIGNAL(valueChanged(int)),
78 		SLOT(changed()));
79 	QObject::connect(m_ui.NameLineEdit,
80 		SIGNAL(textChanged(const QString&)),
81 		SLOT(nameChanged(const QString&)));
82 	QObject::connect(m_ui.EngineNameComboBox,
83 		SIGNAL(activated(int)),
84 		SLOT(changed()));
85 	QObject::connect(m_ui.InstrumentFileComboBox,
86 		SIGNAL(activated(const QString&)),
87 		SLOT(updateInstrumentName()));
88 	QObject::connect(m_ui.InstrumentFileToolButton,
89 		SIGNAL(clicked()),
90 		SLOT(openInstrumentFile()));
91 	QObject::connect(m_ui.InstrumentNrComboBox,
92 		SIGNAL(activated(int)),
93 		SLOT(instrumentNrChanged()));
94 	QObject::connect(m_ui.VolumeSpinBox,
95 		SIGNAL(valueChanged(int)),
96 		SLOT(changed()));
97 	QObject::connect(m_ui.LoadModeComboBox,
98 		SIGNAL(activated(int)),
99 		SLOT(changed()));
100 	QObject::connect(m_ui.DialogButtonBox,
101 		SIGNAL(accepted()),
102 		SLOT(accept()));
103 	QObject::connect(m_ui.DialogButtonBox,
104 		SIGNAL(rejected()),
105 		SLOT(reject()));
106 }
107 
108 
~InstrumentForm(void)109 InstrumentForm::~InstrumentForm (void)
110 {
111 }
112 
113 
114 // Channel dialog setup formal initializer.
setup(Instrument * pInstrument)115 void InstrumentForm::setup ( Instrument *pInstrument )
116 {
117 	m_pInstrument = pInstrument;
118 
119 	m_iDirtySetup = 0;
120 	m_iDirtyCount = 0;
121 	m_iDirtyName  = 0;
122 
123 	if (m_pInstrument == nullptr)
124 		return;
125 
126 	// Check if we're up and connected.
127 	MainForm* pMainForm = MainForm::getInstance();
128 	if (pMainForm == nullptr)
129 		return;
130 	if (pMainForm->client() == nullptr)
131 		return;
132 
133 	Options *pOptions = pMainForm->options();
134 	if (pOptions == nullptr)
135 		return;
136 
137 	// It can be a brand new channel, remember?
138 	bool bNew = (m_pInstrument->bank() < 0 || m_pInstrument->prog() < 0);
139 	if (!bNew) {
140 		m_pInstrument->getInstrument();
141 		m_iDirtyName++;
142 	}
143 
144 	// Avoid nested changes.
145 	m_iDirtySetup++;
146 
147 	// Load combo box history...
148 	pOptions->loadComboBoxHistory(m_ui.InstrumentFileComboBox);
149 
150 	// Populate maps list.
151 	m_ui.MapComboBox->clear();
152 	m_ui.MapComboBox->insertItems(0, Instrument::getMapNames());
153 
154 	// Populate Engines list.
155 	const char **ppszEngines
156 		= ::lscp_list_available_engines(pMainForm->client());
157 	if (ppszEngines) {
158 		m_ui.EngineNameComboBox->clear();
159 		for (int iEngine = 0; ppszEngines[iEngine]; iEngine++)
160 			m_ui.EngineNameComboBox->addItem(ppszEngines[iEngine]);
161 	}
162 	else pMainForm->appendMessagesClient("lscp_list_available_engines");
163 
164 	// Read proper instrument information,
165 	// and populate the instrument form fields.
166 
167 	// Instrument map name...
168 	int iMap = (bNew ? pOptions->iMidiMap : m_pInstrument->map());
169 	if (iMap < 0)
170 		iMap = 0;
171 	const QString& sMapName = Instrument::getMapName(iMap);
172 	if (!sMapName.isEmpty()) {
173 		m_ui.MapComboBox->setCurrentIndex(
174 			m_ui.MapComboBox->findText(sMapName,
175 				Qt::MatchExactly | Qt::MatchCaseSensitive));
176 	}
177 
178 	// It might be no maps around...
179 	bool bMapEnabled = (m_ui.MapComboBox->count() > 0);
180 	m_ui.MapTextLabel->setEnabled(bMapEnabled);
181 	m_ui.MapComboBox->setEnabled(bMapEnabled);
182 
183 	// Instrument bank/program...
184 	int iBank = (bNew ? pOptions->iMidiBank : m_pInstrument->bank());
185 	int iProg = (bNew ? pOptions->iMidiProg : m_pInstrument->prog()) + 1;
186 	if (bNew && iProg > 128) {
187 		iProg = 1;
188 		iBank++;
189 	}
190 	m_ui.BankSpinBox->setValue(iBank);
191 	m_ui.ProgSpinBox->setValue(iProg);
192 
193 	// Instrument name...
194 	m_ui.NameLineEdit->setText(m_pInstrument->name());
195 
196 	// Engine name...
197 	QString sEngineName = m_pInstrument->engineName();
198 	if (sEngineName.isEmpty() || bNew)
199 		sEngineName = pOptions->sEngineName;
200 	if (sEngineName.isEmpty())
201 		sEngineName = Channel::noEngineName();
202 	if (m_ui.EngineNameComboBox->findText(sEngineName,
203 			Qt::MatchExactly | Qt::MatchCaseSensitive) < 0) {
204 		m_ui.EngineNameComboBox->addItem(sEngineName);
205 	}
206 	m_ui.EngineNameComboBox->setCurrentIndex(
207 		m_ui.EngineNameComboBox->findText(sEngineName,
208 			Qt::MatchExactly | Qt::MatchCaseSensitive));
209 
210 	// Instrument filename and index...
211 	QString sInstrumentFile = m_pInstrument->instrumentFile();
212 	if (sInstrumentFile.isEmpty())
213 		sInstrumentFile = Channel::noInstrumentName();
214 	m_ui.InstrumentFileComboBox->setEditText(sInstrumentFile);
215 	m_ui.InstrumentNrComboBox->clear();
216 	m_ui.InstrumentNrComboBox->insertItems(0,
217 		Channel::getInstrumentList(sInstrumentFile,
218 		pOptions->bInstrumentNames));
219 	m_ui.InstrumentNrComboBox->setCurrentIndex(m_pInstrument->instrumentNr());
220 
221 	// Instrument volume....
222 	int iVolume = (bNew ? pOptions->iVolume :
223 		::lroundf(100.0f * m_pInstrument->volume()));
224 	m_ui.VolumeSpinBox->setValue(iVolume);
225 
226 	// Instrument load mode...
227 	int iLoadMode = (bNew ? pOptions->iLoadMode :
228 		m_pInstrument->loadMode());
229 	m_ui.LoadModeComboBox->setCurrentIndex(iLoadMode);
230 
231 	// Done.
232 	m_iDirtySetup--;
233 	stabilizeForm();
234 }
235 
236 
237 // Special case for name change,
nameChanged(const QString &)238 void InstrumentForm::nameChanged ( const QString& /* sName */ )
239 {
240 	if (m_iDirtySetup > 0)
241 		return;
242 
243 	m_iDirtyName++;
244 	changed();
245 }
246 
247 
248 // Browse and open an instrument file.
openInstrumentFile(void)249 void InstrumentForm::openInstrumentFile (void)
250 {
251 	MainForm* pMainForm = MainForm::getInstance();
252 	if (pMainForm == nullptr)
253 		return;
254 
255 	Options *pOptions = pMainForm->options();
256 	if (pOptions == nullptr)
257 		return;
258 
259 	// FIXME: the instrument file filters should be restricted,
260 	// depending on the current engine.
261 	const QString& sEngineName = m_ui.EngineNameComboBox->currentText().toUpper();
262 	QStringList filters;
263 	if (sEngineName.contains("GIG"))
264 		filters << tr("GIG Instrument files") + " (*.gig *.dls)";
265 	if (sEngineName.contains("SFZ"))
266 		filters << tr("SFZ Instrument files") + " (*.sfz)";
267 	if (sEngineName.contains("SF2"))
268 		filters << tr("SF2 Instrument files") + " (*.sf2)";
269 	const QString& filter = filters.join(";;");
270 
271 	QString sInstrumentFile = QFileDialog::getOpenFileName(this,
272 		tr("Instrument files"),   // Caption.
273 		pOptions->sInstrumentDir, // Start here.
274 		filter                    // File filter.
275 	);
276 
277 	if (sInstrumentFile.isEmpty())
278 		return;
279 
280 	m_ui.InstrumentFileComboBox->setEditText(sInstrumentFile);
281 	updateInstrumentName();
282 }
283 
284 
285 // Refresh the actual instrument name.
updateInstrumentName(void)286 void InstrumentForm::updateInstrumentName (void)
287 {
288 	MainForm* pMainForm = MainForm::getInstance();
289 	if (pMainForm == nullptr)
290 		return;
291 
292 	Options *pOptions = pMainForm->options();
293 	if (pOptions == nullptr)
294 		return;
295 
296 	// TODO: this better idea would be to use libgig
297 	// to retrieve the REAL instrument names.
298 	m_ui.InstrumentNrComboBox->clear();
299 	m_ui.InstrumentNrComboBox->insertItems(0,
300 		Channel::getInstrumentList(
301 			m_ui.InstrumentFileComboBox->currentText(),
302 			pOptions->bInstrumentNames)
303 	);
304 
305 	instrumentNrChanged();
306 }
307 
308 
309 // Special case for instrumnet index change,
instrumentNrChanged(void)310 void InstrumentForm::instrumentNrChanged (void)
311 {
312 	if (m_iDirtySetup > 0)
313 		return;
314 
315 	if (m_ui.NameLineEdit->text().isEmpty() || m_iDirtyName == 0) {
316 		m_ui.NameLineEdit->setText(m_ui.InstrumentNrComboBox->currentText());
317 		m_iDirtyName = 0;
318 	}
319 
320 	changed();
321 }
322 
323 
324 // Accept settings (OK button slot).
accept(void)325 void InstrumentForm::accept (void)
326 {
327 	if (m_pInstrument == nullptr)
328 		return;
329 
330 	MainForm* pMainForm = MainForm::getInstance();
331 	if (pMainForm == nullptr)
332 		return;
333 	if (pMainForm->client() == nullptr)
334 		return;
335 
336 	Options *pOptions = pMainForm->options();
337 	if (pOptions == nullptr)
338 		return;
339 
340 	if (m_iDirtyCount > 0) {
341 		m_pInstrument->setMap(m_ui.MapComboBox->currentIndex());
342 		m_pInstrument->setBank(m_ui.BankSpinBox->value());
343 		m_pInstrument->setProg(m_ui.ProgSpinBox->value() - 1);
344 		m_pInstrument->setName(m_ui.NameLineEdit->text());
345 		m_pInstrument->setEngineName(m_ui.EngineNameComboBox->currentText());
346 		m_pInstrument->setInstrumentFile(m_ui.InstrumentFileComboBox->currentText());
347 		m_pInstrument->setInstrumentNr(m_ui.InstrumentNrComboBox->currentIndex());
348 		m_pInstrument->setVolume(0.01f * float(m_ui.VolumeSpinBox->value()));
349 		m_pInstrument->setLoadMode(m_ui.LoadModeComboBox->currentIndex());
350 	}
351 
352 	// Save default engine name, instrument directory and history...
353 	pOptions->sInstrumentDir = QFileInfo(
354 		m_ui.InstrumentFileComboBox->currentText()).dir().absolutePath();
355 	pOptions->sEngineName = m_ui.EngineNameComboBox->currentText();
356 	pOptions->iMidiMap  = m_ui.MapComboBox->currentIndex();
357 	pOptions->iMidiBank = m_ui.BankSpinBox->value();
358 	pOptions->iMidiProg = m_ui.ProgSpinBox->value();
359 	pOptions->iVolume   = m_ui.VolumeSpinBox->value();
360 	pOptions->iLoadMode = m_ui.LoadModeComboBox->currentIndex();
361 	pOptions->saveComboBoxHistory(m_ui.InstrumentFileComboBox);
362 
363 	// Just go with dialog acceptance.
364 	QDialog::accept();
365 }
366 
367 
368 // Reject settings (Cancel button slot).
reject(void)369 void InstrumentForm::reject (void)
370 {
371 	bool bReject = true;
372 
373 	// Check if there's any pending changes...
374 	if (m_iDirtyCount > 0) {
375 		switch (QMessageBox::warning(this,
376 			tr("Warning"),
377 			tr("Some channel settings have been changed.\n\n"
378 			"Do you want to apply the changes?"),
379 			QMessageBox::Apply |
380 			QMessageBox::Discard |
381 			QMessageBox::Cancel)) {
382 		case QMessageBox::Apply:
383 			accept();
384 			return;
385 		case QMessageBox::Discard:
386 			break;
387 		default:    // Cancel.
388 			bReject = false;
389 			break;
390 		}
391 	}
392 
393 	if (bReject)
394 		QDialog::reject();
395 }
396 
397 
398 // Dirty up settings.
changed(void)399 void InstrumentForm::changed (void)
400 {
401 	if (m_iDirtySetup > 0)
402 		return;
403 
404 	m_iDirtyCount++;
405 	stabilizeForm();
406 }
407 
408 
409 // Stabilize current form state.
stabilizeForm(void)410 void InstrumentForm::stabilizeForm (void)
411 {
412 	bool bValid = !m_ui.NameLineEdit->text().isEmpty()
413 		&& m_ui.EngineNameComboBox->currentIndex() >= 0
414 		&& m_ui.EngineNameComboBox->currentText() != Channel::noEngineName();
415 
416 	const QString& sPath = m_ui.InstrumentFileComboBox->currentText();
417 	bValid = bValid && !sPath.isEmpty() && QFileInfo(sPath).exists();
418 
419 	m_ui.DialogButtonBox->button(
420 		QDialogButtonBox::Ok)->setEnabled(m_iDirtyCount > 0 && bValid);
421 }
422 
423 } // namespace QSampler
424 
425 
426 // end of qsamplerInstrumentForm.cpp
427